mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 23:10:20 +08:00
feat(ui): integrate @storybook/addon-themes and enhance CustomCollapse component
- Added @storybook/addon-themes to package.json and yarn.lock for theme support in Storybook. - Updated CustomCollapse component to utilize HeroUI's Accordion and AccordionItem for improved functionality and styling. - Removed the ReasoningIcon component as it was deemed unnecessary. - Enhanced ProviderAvatar component to ensure consistent className handling. - Added new stories for FileIcons, SvgSpinners180Ring, and ToolsCallingIcon to showcase their usage and variations.
This commit is contained in:
parent
355e5b269d
commit
76b3ba5d7e
@ -2,7 +2,7 @@ import type { StorybookConfig } from '@storybook/react-vite'
|
|||||||
|
|
||||||
const config: StorybookConfig = {
|
const config: StorybookConfig = {
|
||||||
stories: ['../stories/components/**/*.stories.@(js|jsx|ts|tsx)'],
|
stories: ['../stories/components/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||||
addons: ['@storybook/addon-docs'],
|
addons: ['@storybook/addon-docs', '@storybook/addon-themes'],
|
||||||
framework: '@storybook/react-vite',
|
framework: '@storybook/react-vite',
|
||||||
viteFinal: async (config) => {
|
viteFinal: async (config) => {
|
||||||
const { mergeConfig } = await import('vite')
|
const { mergeConfig } = await import('vite')
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
import '../stories/tailwind.css'
|
import '../stories/tailwind.css'
|
||||||
|
|
||||||
|
import { withThemeByClassName } from '@storybook/addon-themes'
|
||||||
import type { Preview } from '@storybook/react'
|
import type { Preview } from '@storybook/react'
|
||||||
|
|
||||||
const preview: Preview = {
|
const preview: Preview = {
|
||||||
parameters: {
|
decorators: [
|
||||||
controls: {
|
withThemeByClassName({
|
||||||
matchers: {
|
themes: {
|
||||||
color: /(background|color)$/i,
|
light: '',
|
||||||
date: /Date$/i
|
dark: 'dark'
|
||||||
}
|
},
|
||||||
}
|
defaultTheme: 'light'
|
||||||
}
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default preview
|
export default preview
|
||||||
|
|||||||
@ -49,6 +49,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@heroui/react": "^2.8.4",
|
"@heroui/react": "^2.8.4",
|
||||||
"@storybook/addon-docs": "^9.1.6",
|
"@storybook/addon-docs": "^9.1.6",
|
||||||
|
"@storybook/addon-themes": "^9.1.6",
|
||||||
"@storybook/react-vite": "^9.1.6",
|
"@storybook/react-vite": "^9.1.6",
|
||||||
"@types/react": "^19.0.12",
|
"@types/react": "^19.0.12",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
// Original path: src/renderer/src/components/CustomCollapse.tsx
|
import { Accordion, AccordionItem } from '@heroui/react'
|
||||||
import { ChevronRight } from 'lucide-react'
|
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { memo, useState } from 'react'
|
import { memo, useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
// 重新导出 HeroUI 的组件,方便直接使用
|
||||||
|
export { Accordion, AccordionItem } from '@heroui/react'
|
||||||
|
|
||||||
interface CustomCollapseProps {
|
interface CustomCollapseProps {
|
||||||
label: React.ReactNode
|
label: React.ReactNode
|
||||||
@ -12,59 +14,78 @@ interface CustomCollapseProps {
|
|||||||
activeKey?: string[]
|
activeKey?: string[]
|
||||||
collapsible?: 'header' | 'icon' | 'disabled'
|
collapsible?: 'header' | 'icon' | 'disabled'
|
||||||
onChange?: (activeKeys: string | string[]) => void
|
onChange?: (activeKeys: string | string[]) => void
|
||||||
|
style?: React.CSSProperties
|
||||||
|
classNames?: {
|
||||||
|
trigger?: string
|
||||||
|
content?: string
|
||||||
|
}
|
||||||
className?: string
|
className?: string
|
||||||
|
variant?: 'light' | 'shadow' | 'bordered' | 'splitted'
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomCollapse: FC<CustomCollapseProps> = ({
|
const CustomCollapse: FC<CustomCollapseProps> = ({
|
||||||
label,
|
label,
|
||||||
extra,
|
extra,
|
||||||
children,
|
children,
|
||||||
destroyInactivePanel = false,
|
|
||||||
defaultActiveKey = ['1'],
|
defaultActiveKey = ['1'],
|
||||||
activeKey,
|
activeKey,
|
||||||
collapsible = undefined,
|
collapsible,
|
||||||
onChange,
|
onChange,
|
||||||
className = ''
|
style,
|
||||||
|
classNames,
|
||||||
|
className = '',
|
||||||
|
variant = 'bordered'
|
||||||
}) => {
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(activeKey ? activeKey.includes('1') : defaultActiveKey.includes('1'))
|
const [expandedKeys, setExpandedKeys] = useState<Set<string>>(() => {
|
||||||
|
if (activeKey !== undefined) {
|
||||||
|
return new Set(activeKey)
|
||||||
|
}
|
||||||
|
return new Set(defaultActiveKey)
|
||||||
|
})
|
||||||
|
|
||||||
const handleToggle = () => {
|
useEffect(() => {
|
||||||
if (collapsible === 'disabled') return
|
if (activeKey !== undefined) {
|
||||||
|
setExpandedKeys(new Set(activeKey))
|
||||||
|
}
|
||||||
|
}, [activeKey])
|
||||||
|
|
||||||
const newState = !isOpen
|
const handleSelectionChange = (keys: 'all' | Set<React.Key>) => {
|
||||||
setIsOpen(newState)
|
if (keys === 'all') return
|
||||||
onChange?.(newState ? ['1'] : [])
|
|
||||||
|
const stringKeys = Array.from(keys).map((key) => String(key))
|
||||||
|
const newExpandedKeys = new Set(stringKeys)
|
||||||
|
|
||||||
|
if (activeKey === undefined) {
|
||||||
|
setExpandedKeys(newExpandedKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange?.(stringKeys.length === 1 ? stringKeys[0] : stringKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldRenderContent = !destroyInactivePanel || isOpen
|
const isDisabled = collapsible === 'disabled'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`w-full bg-transparent border border-gray-200 dark:border-gray-700 ${className}`}>
|
<Accordion
|
||||||
<div
|
className={className}
|
||||||
className={`flex items-center justify-between px-4 py-1 cursor-pointer bg-gray-50 dark:bg-gray-800 ${
|
style={style}
|
||||||
isOpen ? 'rounded-t-lg' : 'rounded-lg'
|
variant={variant}
|
||||||
} ${collapsible === 'disabled' ? 'cursor-default' : ''}`}
|
defaultExpandedKeys={activeKey === undefined ? defaultActiveKey : undefined}
|
||||||
onClick={collapsible === 'header' || collapsible === undefined ? handleToggle : undefined}>
|
selectedKeys={activeKey !== undefined ? expandedKeys : undefined}
|
||||||
<div className="flex items-center justify-between w-full">
|
onSelectionChange={handleSelectionChange}
|
||||||
<div className="flex items-center">
|
isDisabled={isDisabled}
|
||||||
{(collapsible === 'icon' || collapsible === undefined) && (
|
selectionMode="multiple">
|
||||||
<div className="mr-2 cursor-pointer" onClick={collapsible === 'icon' ? handleToggle : undefined}>
|
<AccordionItem
|
||||||
<ChevronRight
|
key="1"
|
||||||
size={16}
|
aria-label={typeof label === 'string' ? label : 'collapse-item'}
|
||||||
className={`text-gray-500 dark:text-gray-400 transition-transform duration-200 ${
|
title={label}
|
||||||
isOpen ? 'rotate-90' : 'rotate-0'
|
startContent={extra}
|
||||||
}`}
|
classNames={{
|
||||||
strokeWidth={1.5}
|
trigger: classNames?.trigger ?? '',
|
||||||
/>
|
content: classNames?.content ?? ''
|
||||||
</div>
|
}}>
|
||||||
)}
|
{children}
|
||||||
{label}
|
</AccordionItem>
|
||||||
</div>
|
</Accordion>
|
||||||
{extra && <div>{extra}</div>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isOpen && <div className="border-t-0">{shouldRenderContent && children}</div>}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export const ProviderAvatar: React.FC<ProviderAvatarProps> = ({
|
|||||||
providerName,
|
providerName,
|
||||||
logoSrc,
|
logoSrc,
|
||||||
size = 'md',
|
size = 'md',
|
||||||
className,
|
className = '',
|
||||||
style,
|
style,
|
||||||
renderCustomLogo
|
renderCustomLogo
|
||||||
}) => {
|
}) => {
|
||||||
@ -51,7 +51,7 @@ export const ProviderAvatar: React.FC<ProviderAvatarProps> = ({
|
|||||||
if (customLogo) {
|
if (customLogo) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center justify-center rounded-full border border-gray-200 dark:border-gray-700 ${className || ''}`}
|
className={`flex items-center justify-center rounded-full border border-gray-200 dark:border-gray-700 ${className}`}
|
||||||
style={getCustomStyle()}>
|
style={getCustomStyle()}>
|
||||||
<div className="w-4/5 h-4/5 flex items-center justify-center">{customLogo}</div>
|
<div className="w-4/5 h-4/5 flex items-center justify-center">{customLogo}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -65,7 +65,7 @@ export const ProviderAvatar: React.FC<ProviderAvatarProps> = ({
|
|||||||
<Avatar
|
<Avatar
|
||||||
src={logoSrc}
|
src={logoSrc}
|
||||||
size={getAvatarSize()}
|
size={getAvatarSize()}
|
||||||
className={`border border-gray-200 dark:border-gray-700 ${className || ''}`}
|
className={`border border-gray-200 dark:border-gray-700 ${className}`}
|
||||||
style={getCustomStyle()}
|
style={getCustomStyle()}
|
||||||
imgProps={{ draggable: false }}
|
imgProps={{ draggable: false }}
|
||||||
/>
|
/>
|
||||||
@ -80,7 +80,7 @@ export const ProviderAvatar: React.FC<ProviderAvatarProps> = ({
|
|||||||
<Avatar
|
<Avatar
|
||||||
name={getFirstCharacter(providerName)}
|
name={getFirstCharacter(providerName)}
|
||||||
size={getAvatarSize()}
|
size={getAvatarSize()}
|
||||||
className={`border border-gray-200 dark:border-gray-700 ${className || ''}`}
|
className={`border border-gray-200 dark:border-gray-700 ${className}`}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
color,
|
color,
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
// Original path: src/renderer/src/components/Icons/ReasoningIcon.tsx
|
|
||||||
import { Tooltip } from 'antd'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
const ReasoningIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>> = (props) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<Tooltip title={t('models.type.reasoning')} placement="top">
|
|
||||||
<Icon className="iconfont icon-thinking" {...(props as any)} />
|
|
||||||
</Tooltip>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
`
|
|
||||||
|
|
||||||
const Icon = styled.i`
|
|
||||||
color: var(--color-link);
|
|
||||||
font-size: 16px;
|
|
||||||
margin-right: 6px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default ReasoningIcon
|
|
||||||
@ -1,8 +1,14 @@
|
|||||||
// Original path: src/renderer/src/components/Icons/SvgSpinners180Ring.tsx
|
// Original path: src/renderer/src/components/Icons/SvgSpinners180Ring.tsx
|
||||||
import type { SVGProps } from 'react'
|
import type { SVGProps } from 'react'
|
||||||
|
|
||||||
export function SvgSpinners180Ring(props: SVGProps<SVGSVGElement> & { size?: number | string }) {
|
import { cn } from '../../../utils'
|
||||||
const { size = '1em', ...svgProps } = props
|
|
||||||
|
interface SvgSpinners180RingProps extends SVGProps<SVGSVGElement> {
|
||||||
|
size?: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SvgSpinners180Ring(props: SvgSpinners180RingProps) {
|
||||||
|
const { size = '1em', className, ...svgProps } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
@ -11,7 +17,7 @@ export function SvgSpinners180Ring(props: SVGProps<SVGSVGElement> & { size?: num
|
|||||||
height={size}
|
height={size}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
{...svgProps}
|
{...svgProps}
|
||||||
className={`animation-rotate ${svgProps.className || ''}`.trim()}>
|
className={cn('animate-spin', className)}>
|
||||||
{/* Icon from SVG Spinners by Utkarsh Verma - https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE */}
|
{/* Icon from SVG Spinners by Utkarsh Verma - https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE */}
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@ -19,4 +25,5 @@ export function SvgSpinners180Ring(props: SVGProps<SVGSVGElement> & { size?: num
|
|||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SvgSpinners180Ring
|
export default SvgSpinners180Ring
|
||||||
|
|||||||
@ -1,33 +1,26 @@
|
|||||||
// Original: src/renderer/src/components/Icons/ToolsCallingIcon.tsx
|
// Original: src/renderer/src/components/Icons/ToolsCallingIcon.tsx
|
||||||
import { ToolOutlined } from '@ant-design/icons'
|
import { Tooltip } from '@heroui/react'
|
||||||
import { Tooltip } from 'antd'
|
import { Wrench } from 'lucide-react'
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
const ToolsCallingIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>> = (props) => {
|
import { cn } from '../../../utils'
|
||||||
|
|
||||||
|
interface ToolsCallingIconProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
className?: string
|
||||||
|
iconClassName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ToolsCallingIcon = ({ className, iconClassName, ...props }: ToolsCallingIconProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<div className={cn('flex justify-center items-center', className)} {...props}>
|
||||||
<Tooltip title={t('models.function_calling')} placement="top">
|
<Tooltip content={t('models.function_calling')} placement="top">
|
||||||
<Icon {...(props as any)} />
|
<Wrench className={cn('w-4 h-4 mr-1.5 text-[#00b96b]', iconClassName)} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Container>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
`
|
|
||||||
|
|
||||||
const Icon = styled(ToolOutlined)`
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 15px;
|
|
||||||
margin-right: 6px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default ToolsCallingIcon
|
export default ToolsCallingIcon
|
||||||
|
|||||||
@ -42,7 +42,6 @@ export {
|
|||||||
WebSearchIcon,
|
WebSearchIcon,
|
||||||
WrapIcon
|
WrapIcon
|
||||||
} from './icons/Icon'
|
} from './icons/Icon'
|
||||||
export { default as ReasoningIcon } from './icons/ReasoningIcon'
|
|
||||||
export { default as SvgSpinners180Ring } from './icons/SvgSpinners180Ring'
|
export { default as SvgSpinners180Ring } from './icons/SvgSpinners180Ring'
|
||||||
export { default as ToolsCallingIcon } from './icons/ToolsCallingIcon'
|
export { default as ToolsCallingIcon } from './icons/ToolsCallingIcon'
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { Button } from '@heroui/react'
|
import { Button } from '@heroui/react'
|
||||||
import type { Meta, StoryObj } from '@storybook/react'
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
import { AlertTriangle, Info, Settings } from 'lucide-react'
|
import { AlertTriangle, CreditCard, Info, Monitor, Settings, Shield } from 'lucide-react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import CustomCollapse from '../../../src/components/base/CustomCollapse'
|
import CustomCollapse, { Accordion, AccordionItem } from '../../../src/components/base/CustomCollapse'
|
||||||
|
|
||||||
const meta: Meta<typeof CustomCollapse> = {
|
const meta: Meta<typeof CustomCollapse> = {
|
||||||
title: 'Base/CustomCollapse',
|
title: 'Base/CustomCollapse',
|
||||||
@ -14,20 +14,16 @@ const meta: Meta<typeof CustomCollapse> = {
|
|||||||
tags: ['autodocs'],
|
tags: ['autodocs'],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
label: {
|
label: {
|
||||||
control: false,
|
control: 'text',
|
||||||
description: '折叠面板的标题内容'
|
description: '面板标题'
|
||||||
},
|
},
|
||||||
extra: {
|
extra: {
|
||||||
control: false,
|
control: false,
|
||||||
description: '标题栏右侧的额外内容'
|
description: '额外内容(副标题)'
|
||||||
},
|
},
|
||||||
children: {
|
children: {
|
||||||
control: false,
|
control: false,
|
||||||
description: '折叠面板的内容'
|
description: '面板内容'
|
||||||
},
|
|
||||||
destroyInactivePanel: {
|
|
||||||
control: 'boolean',
|
|
||||||
description: '是否销毁非活动面板的内容'
|
|
||||||
},
|
},
|
||||||
defaultActiveKey: {
|
defaultActiveKey: {
|
||||||
control: false,
|
control: false,
|
||||||
@ -37,14 +33,35 @@ const meta: Meta<typeof CustomCollapse> = {
|
|||||||
control: false,
|
control: false,
|
||||||
description: '当前激活的面板键值(受控模式)'
|
description: '当前激活的面板键值(受控模式)'
|
||||||
},
|
},
|
||||||
collapsible: {
|
|
||||||
control: 'select',
|
|
||||||
options: ['header', 'icon', 'disabled', undefined],
|
|
||||||
description: '折叠触发方式'
|
|
||||||
},
|
|
||||||
onChange: {
|
onChange: {
|
||||||
control: false,
|
control: false,
|
||||||
description: '面板状态变化回调'
|
description: '面板状态变化回调'
|
||||||
|
},
|
||||||
|
collapsible: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['header', 'icon', 'disabled'],
|
||||||
|
description: '折叠触发方式'
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
control: 'text',
|
||||||
|
description: '额外的 CSS 类名'
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['light', 'shadow', 'bordered', 'splitted'],
|
||||||
|
description: 'HeroUI 样式变体'
|
||||||
|
},
|
||||||
|
destroyInactivePanel: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: '是否销毁非激活面板'
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
control: false,
|
||||||
|
description: '自定义样式'
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
control: false,
|
||||||
|
description: '自定义头部和内容样式'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,6 +69,7 @@ const meta: Meta<typeof CustomCollapse> = {
|
|||||||
export default meta
|
export default meta
|
||||||
type Story = StoryObj<typeof meta>
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
|
// 基础用法
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
label: '默认折叠面板',
|
label: '默认折叠面板',
|
||||||
@ -64,28 +82,78 @@ export const Default: Story = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WithExtra: Story = {
|
// 带副标题
|
||||||
|
export const WithSubtitle: Story = {
|
||||||
args: {
|
args: {
|
||||||
label: '带额外内容的面板',
|
label: '带副标题的折叠面板',
|
||||||
extra: (
|
extra: <span className="text-sm text-gray-500">这是副标题内容</span>,
|
||||||
<Button size="sm" variant="ghost">
|
defaultActiveKey: ['1'],
|
||||||
编辑
|
|
||||||
</Button>
|
|
||||||
),
|
|
||||||
children: (
|
children: (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<p>这个面板在标题栏右侧有一个额外的按钮。</p>
|
<p>面板内容</p>
|
||||||
<p>额外内容不会触发折叠/展开操作。</p>
|
<p>可以在 extra 属性中设置副标题</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WithIcon: Story = {
|
// HeroUI 样式变体
|
||||||
|
export const VariantLight: Story = {
|
||||||
|
args: {
|
||||||
|
label: 'Light 变体',
|
||||||
|
variant: 'light',
|
||||||
|
children: (
|
||||||
|
<div className="p-4">
|
||||||
|
<p>这是 HeroUI 的 Light 变体样式。</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VariantShadow: Story = {
|
||||||
|
args: {
|
||||||
|
label: 'Shadow 变体',
|
||||||
|
extra: '带阴影的面板样式',
|
||||||
|
variant: 'shadow',
|
||||||
|
className: 'p-2',
|
||||||
|
children: (
|
||||||
|
<div className="p-4">
|
||||||
|
<p>这是 HeroUI 的 Shadow 变体样式。</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VariantBordered: Story = {
|
||||||
|
args: {
|
||||||
|
label: 'Bordered 变体(默认)',
|
||||||
|
variant: 'bordered',
|
||||||
|
children: (
|
||||||
|
<div className="p-4">
|
||||||
|
<p>这是 HeroUI 的 Bordered 变体样式。</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VariantSplitted: Story = {
|
||||||
|
args: {
|
||||||
|
label: 'Splitted 变体',
|
||||||
|
variant: 'splitted',
|
||||||
|
children: (
|
||||||
|
<div className="p-4">
|
||||||
|
<p>这是 HeroUI 的 Splitted 变体样式。</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 富内容标题
|
||||||
|
export const RichLabel: Story = {
|
||||||
args: {
|
args: {
|
||||||
label: (
|
label: (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Settings size={16} />
|
<Settings className="text-default-500" size={20} />
|
||||||
<span>设置面板</span>
|
<span>设置面板</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@ -110,71 +178,172 @@ export const WithIcon: Story = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CollapsibleHeader: Story = {
|
// 带警告提示
|
||||||
|
export const WithWarning: Story = {
|
||||||
args: {
|
args: {
|
||||||
label: '点击整个标题栏展开/收起',
|
label: (
|
||||||
collapsible: 'header',
|
<div className="flex items-center gap-2">
|
||||||
|
<Monitor className="text-primary" size={20} />
|
||||||
|
<span>连接的设备</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
extra: (
|
||||||
|
<p className="flex">
|
||||||
|
2个问题需要<span className="text-primary ml-1">立即修复</span>
|
||||||
|
</p>
|
||||||
|
),
|
||||||
children: (
|
children: (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<p>通过设置 collapsible="header",点击整个标题栏都可以触发折叠/展开。</p>
|
<p className="text-small">检测到以下设备连接异常:</p>
|
||||||
</div>
|
<ul className="list-disc list-inside mt-2 text-small space-y-1">
|
||||||
)
|
<li>外部显示器连接不稳定</li>
|
||||||
}
|
<li>蓝牙键盘配对失败</li>
|
||||||
}
|
</ul>
|
||||||
|
|
||||||
export const CollapsibleIcon: Story = {
|
|
||||||
args: {
|
|
||||||
label: '仅点击图标展开/收起',
|
|
||||||
collapsible: 'icon',
|
|
||||||
children: (
|
|
||||||
<div className="p-4">
|
|
||||||
<p>通过设置 collapsible="icon",只有点击左侧的箭头图标才能触发折叠/展开。</p>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 禁用状态
|
||||||
export const Disabled: Story = {
|
export const Disabled: Story = {
|
||||||
args: {
|
args: {
|
||||||
label: '禁用的折叠面板',
|
label: '禁用的折叠面板',
|
||||||
collapsible: 'disabled',
|
collapsible: 'disabled',
|
||||||
|
defaultActiveKey: ['1'],
|
||||||
children: (
|
children: (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<p>这个面板被禁用了,无法折叠或展开。</p>
|
<p>这个面板被禁用了,无法操作。</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DestroyInactivePanel: Story = {
|
// 受控模式
|
||||||
args: {
|
export const ControlledMode: Story = {
|
||||||
label: '销毁非活动内容',
|
render: function ControlledMode() {
|
||||||
destroyInactivePanel: true,
|
const [activeKey, setActiveKey] = useState<string[]>(['1'])
|
||||||
children: (
|
|
||||||
<div className="p-4">
|
return (
|
||||||
<p>当 destroyInactivePanel=true 时,面板收起时会销毁内容,展开时重新渲染。</p>
|
<div className="space-y-4">
|
||||||
<p>当前时间:{new Date().toLocaleTimeString()}</p>
|
<div className="flex gap-2">
|
||||||
|
<Button size="sm" onPress={() => setActiveKey(['1'])} color="primary">
|
||||||
|
展开
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" onPress={() => setActiveKey([])} color="default">
|
||||||
|
收起
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<CustomCollapse
|
||||||
|
label="受控的折叠面板"
|
||||||
|
activeKey={activeKey}
|
||||||
|
onChange={(keys) => setActiveKey(Array.isArray(keys) ? keys : [keys])}>
|
||||||
|
<div className="p-4">
|
||||||
|
<p>这是一个受控的折叠面板</p>
|
||||||
|
<p>通过按钮控制展开和收起状态</p>
|
||||||
|
</div>
|
||||||
|
</CustomCollapse>
|
||||||
|
<div className="text-sm text-gray-600">当前状态:{activeKey.length > 0 ? '展开' : '收起'}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 多个单面板组合
|
||||||
|
export const MultipleSinglePanels: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<CustomCollapse label="第一个面板" defaultActiveKey={['1']}>
|
||||||
|
<div className="p-4">
|
||||||
|
<p>第一个面板的内容</p>
|
||||||
|
</div>
|
||||||
|
</CustomCollapse>
|
||||||
|
<CustomCollapse label="第二个面板" extra="带副标题">
|
||||||
|
<div className="p-4">
|
||||||
|
<p>第二个面板的内容</p>
|
||||||
|
</div>
|
||||||
|
</CustomCollapse>
|
||||||
|
<CustomCollapse label="第三个面板(禁用)" collapsible="disabled">
|
||||||
|
<div className="p-4">
|
||||||
|
<p>这个面板被禁用了</p>
|
||||||
|
</div>
|
||||||
|
</CustomCollapse>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用原生 HeroUI Accordion 的多面板示例
|
||||||
|
export const NativeAccordionMultiple: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="max-w-lg">
|
||||||
|
<h3 className="text-lg font-medium mb-4">原生 HeroUI Accordion 多面板</h3>
|
||||||
|
<Accordion variant="shadow" className="p-2 flex flex-col gap-1" defaultExpandedKeys={['1']}>
|
||||||
|
<AccordionItem
|
||||||
|
key="1"
|
||||||
|
title="连接的设备"
|
||||||
|
startContent={<Monitor className="text-primary" size={20} />}
|
||||||
|
subtitle={
|
||||||
|
<p className="flex">
|
||||||
|
2个问题需要<span className="text-primary ml-1">立即修复</span>
|
||||||
|
</p>
|
||||||
|
}>
|
||||||
|
<div className="p-4">
|
||||||
|
<p className="text-small">设备连接状态监控</p>
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem
|
||||||
|
key="2"
|
||||||
|
title="应用权限"
|
||||||
|
startContent={<Shield className="text-default-500" size={20} />}
|
||||||
|
subtitle="3个应用有读取权限">
|
||||||
|
<div className="p-4">
|
||||||
|
<p className="text-small">管理应用的系统权限设置</p>
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem
|
||||||
|
key="3"
|
||||||
|
title="待办任务"
|
||||||
|
startContent={<Info className="text-warning" size={20} />}
|
||||||
|
subtitle={<span className="text-warning">请完善您的个人资料</span>}>
|
||||||
|
<div className="p-4">
|
||||||
|
<p className="text-small">您还有一些信息需要完善</p>
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem
|
||||||
|
key="4"
|
||||||
|
title={
|
||||||
|
<p className="flex gap-1 items-center">
|
||||||
|
卡片已过期
|
||||||
|
<span className="text-default-400 text-small">*4812</span>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
startContent={<CreditCard className="text-danger" size={20} />}
|
||||||
|
subtitle={<span className="text-danger">请立即更新</span>}>
|
||||||
|
<div className="p-4">
|
||||||
|
<p className="text-small text-danger">您的信用卡已过期,请更新支付信息</p>
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 富内容面板
|
||||||
export const RichContent: Story = {
|
export const RichContent: Story = {
|
||||||
args: {
|
args: {
|
||||||
label: (
|
label: (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center justify-between w-full">
|
||||||
<Info size={16} />
|
<div className="flex items-center gap-2">
|
||||||
<span>详细信息</span>
|
<Info className="text-default-500" size={20} />
|
||||||
</div>
|
<span>详细信息</span>
|
||||||
),
|
</div>
|
||||||
extra: (
|
<div className="flex gap-2" onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="flex gap-2">
|
<Button size="sm" variant="flat" color="primary">
|
||||||
<Button size="sm" variant="flat" color="primary">
|
保存
|
||||||
保存
|
</Button>
|
||||||
</Button>
|
<Button size="sm" variant="flat">
|
||||||
<Button size="sm" variant="flat">
|
取消
|
||||||
取消
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
children: (
|
children: (
|
||||||
@ -202,82 +371,82 @@ export const RichContent: Story = {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium mb-1">描述</label>
|
<label className="block text-sm font-medium mb-1">描述</label>
|
||||||
<textarea className="w-full px-3 py-2 border border-gray-300 rounded-md" rows={3} placeholder="请输入描述" />
|
<textarea
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md"
|
||||||
|
rows={3}
|
||||||
|
placeholder="请输入描述"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MultipleCollapse: Story = {
|
// 自定义样式
|
||||||
render: () => (
|
export const CustomStyles: Story = {
|
||||||
<div className="space-y-4">
|
args: {
|
||||||
<h3 className="text-lg font-medium">多个折叠面板</h3>
|
label: (
|
||||||
<div className="space-y-2">
|
<div className="flex items-center gap-2">
|
||||||
<CustomCollapse
|
<AlertTriangle className="text-warning" size={16} />
|
||||||
label="面板 1"
|
<span>警告面板</span>
|
||||||
defaultActiveKey={['1']}
|
|
||||||
children={
|
|
||||||
<div className="p-4">
|
|
||||||
<p>第一个面板的内容</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<CustomCollapse
|
|
||||||
label={
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<AlertTriangle size={16} className="text-yellow-500" />
|
|
||||||
<span>警告面板</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
defaultActiveKey={[]}
|
|
||||||
children={
|
|
||||||
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20">
|
|
||||||
<p className="text-yellow-800 dark:text-yellow-200">这是一个警告面板,默认收起状态。</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<CustomCollapse
|
|
||||||
label="面板 3"
|
|
||||||
collapsible="icon"
|
|
||||||
extra={<span className="text-sm text-gray-500">仅图标可点击</span>}
|
|
||||||
defaultActiveKey={[]}
|
|
||||||
children={
|
|
||||||
<div className="p-4">
|
|
||||||
<p>只能通过点击左侧箭头图标来展开/收起这个面板</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
),
|
||||||
)
|
style: {
|
||||||
|
backgroundColor: 'rgba(255, 193, 7, 0.1)',
|
||||||
|
borderColor: 'var(--color-warning)'
|
||||||
|
},
|
||||||
|
children: (
|
||||||
|
<div className="p-4 bg-warning-50 dark:bg-warning-900/20">
|
||||||
|
<p className="text-warning-800 dark:text-warning-200">这是一个带有自定义样式的警告面板。</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ControlledMode: Story = {
|
// 原生 HeroUI Accordion 多面板受控模式
|
||||||
render: function ControlledMode() {
|
export const NativeAccordionControlled: Story = {
|
||||||
const [activeKey, setActiveKey] = useState<string[]>(['1'])
|
render: function NativeAccordionControlled() {
|
||||||
|
const [activeKeys, setActiveKeys] = useState<Set<string>>(new Set(['1']))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button size="sm" onPress={() => setActiveKey(['1'])} color={activeKey.includes('1') ? 'primary' : 'default'}>
|
<Button size="sm" onPress={() => setActiveKeys(new Set(['1', '2', '3']))} color="primary">
|
||||||
展开
|
全部展开
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" onPress={() => setActiveKey([])} color={!activeKey.includes('1') ? 'primary' : 'default'}>
|
<Button size="sm" onPress={() => setActiveKeys(new Set())} color="default">
|
||||||
收起
|
全部收起
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" onPress={() => setActiveKeys(new Set(['2']))} color="default">
|
||||||
|
只展开第二个
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<CustomCollapse
|
<Accordion
|
||||||
label="受控模式"
|
selectedKeys={activeKeys}
|
||||||
activeKey={activeKey}
|
onSelectionChange={(keys) => {
|
||||||
onChange={(keys) => setActiveKey(Array.isArray(keys) ? keys : [keys])}
|
if (keys !== 'all') {
|
||||||
children={
|
setActiveKeys(keys)
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<AccordionItem key="1" title="受控面板 1">
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<p>这个面板的展开/收起状态由外部控制</p>
|
<p>第一个面板的内容</p>
|
||||||
<p>当前状态:{activeKey.includes('1') ? '展开' : '收起'}</p>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</AccordionItem>
|
||||||
/>
|
<AccordionItem key="2" title="受控面板 2">
|
||||||
|
<div className="p-4">
|
||||||
|
<p>第二个面板的内容</p>
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem key="3" title="受控面板 3">
|
||||||
|
<div className="p-4">
|
||||||
|
<p>第三个面板的内容</p>
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
<div className="text-sm text-gray-600">
|
||||||
|
当前展开的面板:{activeKeys.size > 0 ? Array.from(activeKeys).join(', ') : '无'}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
270
packages/ui/stories/components/icons/FileIcons.stories.tsx
Normal file
270
packages/ui/stories/components/icons/FileIcons.stories.tsx
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
|
import { FilePngIcon, FileSvgIcon } from '../../../src/components/icons/FileIcons'
|
||||||
|
|
||||||
|
// Create a dummy component for the story
|
||||||
|
const FileIconsShowcase = () => <div />
|
||||||
|
|
||||||
|
const meta: Meta<typeof FileIconsShowcase> = {
|
||||||
|
title: 'Icons/FileIcons',
|
||||||
|
component: FileIconsShowcase,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered'
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
size: {
|
||||||
|
description: '图标大小',
|
||||||
|
control: { type: 'text' },
|
||||||
|
defaultValue: '1.1em'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
|
// Basic File Icons
|
||||||
|
export const BasicFileIcons: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">文件类型图标 (默认尺寸: 1.1em)</h3>
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FileSvgIcon />
|
||||||
|
<span className="text-xs text-gray-600">SVG 文件</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FilePngIcon />
|
||||||
|
<span className="text-xs text-gray-600">PNG 文件</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different Sizes
|
||||||
|
export const DifferentSizes: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">不同尺寸的 SVG 图标</h3>
|
||||||
|
<div className="flex items-end gap-4">
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FileSvgIcon size="16" />
|
||||||
|
<span className="text-xs text-gray-600">16px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FileSvgIcon size="24" />
|
||||||
|
<span className="text-xs text-gray-600">24px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FileSvgIcon size="32" />
|
||||||
|
<span className="text-xs text-gray-600">32px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FileSvgIcon size="48" />
|
||||||
|
<span className="text-xs text-gray-600">48px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FileSvgIcon size="64" />
|
||||||
|
<span className="text-xs text-gray-600">64px</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">不同尺寸的 PNG 图标</h3>
|
||||||
|
<div className="flex items-end gap-4">
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FilePngIcon size="16" />
|
||||||
|
<span className="text-xs text-gray-600">16px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FilePngIcon size="24" />
|
||||||
|
<span className="text-xs text-gray-600">24px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FilePngIcon size="32" />
|
||||||
|
<span className="text-xs text-gray-600">32px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FilePngIcon size="48" />
|
||||||
|
<span className="text-xs text-gray-600">48px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FilePngIcon size="64" />
|
||||||
|
<span className="text-xs text-gray-600">64px</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom Colors
|
||||||
|
export const CustomColors: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">自定义颜色 - SVG 图标</h3>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FileSvgIcon size="32" color="#3B82F6" />
|
||||||
|
<span className="text-xs text-gray-600">蓝色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FileSvgIcon size="32" color="#10B981" />
|
||||||
|
<span className="text-xs text-gray-600">绿色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FileSvgIcon size="32" color="#F59E0B" />
|
||||||
|
<span className="text-xs text-gray-600">橙色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FileSvgIcon size="32" color="#EF4444" />
|
||||||
|
<span className="text-xs text-gray-600">红色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FileSvgIcon size="32" color="#8B5CF6" />
|
||||||
|
<span className="text-xs text-gray-600">紫色</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">自定义颜色 - PNG 图标</h3>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FilePngIcon size="32" color="#3B82F6" />
|
||||||
|
<span className="text-xs text-gray-600">蓝色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FilePngIcon size="32" color="#10B981" />
|
||||||
|
<span className="text-xs text-gray-600">绿色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FilePngIcon size="32" color="#F59E0B" />
|
||||||
|
<span className="text-xs text-gray-600">橙色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FilePngIcon size="32" color="#EF4444" />
|
||||||
|
<span className="text-xs text-gray-600">红色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<FilePngIcon size="32" color="#8B5CF6" />
|
||||||
|
<span className="text-xs text-gray-600">紫色</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In File List Context
|
||||||
|
export const InFileListContext: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="mb-3 font-semibold">文件列表中的使用示例</h3>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-gray-200 p-4">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-3 rounded p-2 hover:bg-gray-50">
|
||||||
|
<FileSvgIcon size="20" />
|
||||||
|
<span className="flex-1">illustration.svg</span>
|
||||||
|
<span className="text-xs text-gray-500">45 KB</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 rounded p-2 hover:bg-gray-50">
|
||||||
|
<FilePngIcon size="20" />
|
||||||
|
<span className="flex-1">screenshot.png</span>
|
||||||
|
<span className="text-xs text-gray-500">1.2 MB</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 rounded p-2 hover:bg-gray-50">
|
||||||
|
<FileSvgIcon size="20" />
|
||||||
|
<span className="flex-1">logo.svg</span>
|
||||||
|
<span className="text-xs text-gray-500">12 KB</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 rounded p-2 hover:bg-gray-50">
|
||||||
|
<FilePngIcon size="20" />
|
||||||
|
<span className="flex-1">background.png</span>
|
||||||
|
<span className="text-xs text-gray-500">2.8 MB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// File Type Grid
|
||||||
|
export const FileTypeGrid: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="mb-3 font-semibold">文件类型网格展示</h3>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-4 gap-4">
|
||||||
|
<div className="flex flex-col items-center gap-2 rounded-lg border border-gray-200 p-4 hover:border-blue-500">
|
||||||
|
<FileSvgIcon size="48" />
|
||||||
|
<span className="text-sm font-medium">SVG</span>
|
||||||
|
<span className="text-xs text-gray-600">矢量图形</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center gap-2 rounded-lg border border-gray-200 p-4 hover:border-blue-500">
|
||||||
|
<FilePngIcon size="48" />
|
||||||
|
<span className="text-sm font-medium">PNG</span>
|
||||||
|
<span className="text-xs text-gray-600">位图图像</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center gap-2 rounded-lg border border-gray-200 p-4 hover:border-blue-500">
|
||||||
|
<FileSvgIcon size="48" color="#10B981" />
|
||||||
|
<span className="text-sm font-medium">SVG</span>
|
||||||
|
<span className="text-xs text-gray-600">已处理</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center gap-2 rounded-lg border border-gray-200 p-4 hover:border-blue-500">
|
||||||
|
<FilePngIcon size="48" color="#EF4444" />
|
||||||
|
<span className="text-sm font-medium">PNG</span>
|
||||||
|
<span className="text-xs text-gray-600">错误状态</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interactive Example
|
||||||
|
export const InteractiveExample: Story = {
|
||||||
|
render: () => {
|
||||||
|
const fileTypes = [
|
||||||
|
{ icon: FileSvgIcon, name: 'Vector Graphics', ext: 'SVG', color: '#3B82F6' },
|
||||||
|
{ icon: FilePngIcon, name: 'Raster Image', ext: 'PNG', color: '#10B981' }
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="mb-3 font-semibold">交互式文件类型选择器</h3>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
{fileTypes.map((fileType, index) => {
|
||||||
|
const IconComponent = fileType.icon
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
type="button"
|
||||||
|
className="flex items-center gap-3 rounded-lg border border-gray-200 p-4 text-left transition-all hover:border-blue-500 hover:shadow-md focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20">
|
||||||
|
<IconComponent size="32" color={fileType.color} />
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">{fileType.ext} 文件</div>
|
||||||
|
<div className="text-sm text-gray-600">{fileType.name}</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -25,7 +25,22 @@ const meta: Meta<typeof IconShowcase> = {
|
|||||||
parameters: {
|
parameters: {
|
||||||
layout: 'centered'
|
layout: 'centered'
|
||||||
},
|
},
|
||||||
tags: ['autodocs']
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
size: {
|
||||||
|
description: '图标大小 (支持数字或字符串)',
|
||||||
|
control: { type: 'text' },
|
||||||
|
defaultValue: '1rem'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
description: '图标颜色',
|
||||||
|
control: { type: 'color' }
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
description: '自定义 CSS 类名',
|
||||||
|
control: { type: 'text' }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default meta
|
export default meta
|
||||||
@ -36,7 +51,7 @@ export const PredefinedIcons: Story = {
|
|||||||
render: () => (
|
render: () => (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-3 font-semibold">Predefined Icons (Default Size: 1rem)</h3>
|
<h3 className="mb-3 font-semibold">预定义图标 (默认尺寸: 1rem)</h3>
|
||||||
<div className="grid grid-cols-6 gap-4">
|
<div className="grid grid-cols-6 gap-4">
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-2">
|
||||||
<CopyIcon />
|
<CopyIcon />
|
||||||
@ -138,7 +153,7 @@ export const CustomIconCreation: Story = {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-3 font-semibold">Custom Icons Created with Factory</h3>
|
<h3 className="mb-3 font-semibold">使用工厂函数创建的自定义图标</h3>
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-2">
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
@ -160,7 +175,7 @@ export const CustomIconCreation: Story = {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-3 font-semibold">Override Default Size</h3>
|
<h3 className="mb-3 font-semibold">覆盖默认尺寸</h3>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<SettingsIcon size={32} />
|
<SettingsIcon size={32} />
|
||||||
<DownloadIcon size={32} />
|
<DownloadIcon size={32} />
|
||||||
|
|||||||
@ -0,0 +1,340 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
|
import SvgSpinners180Ring from '../../../src/components/icons/SvgSpinners180Ring'
|
||||||
|
|
||||||
|
const meta: Meta<typeof SvgSpinners180Ring> = {
|
||||||
|
title: 'Icons/SvgSpinners180Ring',
|
||||||
|
component: SvgSpinners180Ring,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered'
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
size: {
|
||||||
|
description: '加载图标大小',
|
||||||
|
control: { type: 'text' },
|
||||||
|
defaultValue: '1em'
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
description: '自定义 CSS 类名',
|
||||||
|
control: { type: 'text' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
|
// Basic Spinner
|
||||||
|
export const BasicSpinner: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">基础加载动画</h3>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<SvgSpinners180Ring />
|
||||||
|
<span className="text-sm text-gray-600">默认尺寸 (1em)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different Sizes
|
||||||
|
export const DifferentSizes: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">不同尺寸的加载动画</h3>
|
||||||
|
<div className="flex items-end gap-6">
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="12" />
|
||||||
|
<span className="text-xs text-gray-600">12px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="16" />
|
||||||
|
<span className="text-xs text-gray-600">16px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="20" />
|
||||||
|
<span className="text-xs text-gray-600">20px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="24" />
|
||||||
|
<span className="text-xs text-gray-600">24px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="32" />
|
||||||
|
<span className="text-xs text-gray-600">32px</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="48" />
|
||||||
|
<span className="text-xs text-gray-600">48px</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different Colors
|
||||||
|
export const DifferentColors: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">不同颜色的加载动画</h3>
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="24" className="text-blue-500" />
|
||||||
|
<span className="text-xs text-gray-600">蓝色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="24" className="text-green-500" />
|
||||||
|
<span className="text-xs text-gray-600">绿色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="24" className="text-orange-500" />
|
||||||
|
<span className="text-xs text-gray-600">橙色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="24" className="text-red-500" />
|
||||||
|
<span className="text-xs text-gray-600">红色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="24" className="text-purple-500" />
|
||||||
|
<span className="text-xs text-gray-600">紫色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="24" className="text-gray-500" />
|
||||||
|
<span className="text-xs text-gray-600">灰色</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading States in Buttons
|
||||||
|
export const LoadingStatesInButtons: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">按钮中的加载状态</h3>
|
||||||
|
<div className="flex flex-wrap items-center gap-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex items-center gap-2 rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<SvgSpinners180Ring size="16" />
|
||||||
|
<span>加载中...</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex items-center gap-2 rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<SvgSpinners180Ring size="16" />
|
||||||
|
<span>保存中</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex items-center gap-2 rounded bg-orange-500 px-4 py-2 text-white hover:bg-orange-600"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<SvgSpinners180Ring size="16" />
|
||||||
|
<span>上传中</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex items-center gap-2 rounded border border-gray-300 bg-white px-4 py-2 text-gray-700 hover:bg-gray-50"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<SvgSpinners180Ring size="16" className="text-gray-500" />
|
||||||
|
<span>处理中</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading Cards
|
||||||
|
export const LoadingCards: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">加载状态卡片</h3>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="rounded-lg border border-gray-200 p-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<SvgSpinners180Ring size="20" className="text-blue-500" />
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium">AI 模型响应中</h4>
|
||||||
|
<p className="text-sm text-gray-600">正在生成回复...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-gray-200 p-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<SvgSpinners180Ring size="20" className="text-green-500" />
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium">文件上传中</h4>
|
||||||
|
<p className="text-sm text-gray-600">75% 完成</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-gray-200 p-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<SvgSpinners180Ring size="20" className="text-orange-500" />
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium">数据同步中</h4>
|
||||||
|
<p className="text-sm text-gray-600">请稍候...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-gray-200 p-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<SvgSpinners180Ring size="20" className="text-purple-500" />
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium">模型训练中</h4>
|
||||||
|
<p className="text-sm text-gray-600">预计2分钟</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inline Loading States
|
||||||
|
export const InlineLoadingStates: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">行内加载状态</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="14" className="text-blue-500" />
|
||||||
|
<span className="text-sm">正在检查网络连接...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="14" className="text-green-500" />
|
||||||
|
<span className="text-sm">正在保存更改...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="14" className="text-orange-500" />
|
||||||
|
<span className="text-sm">正在验证凭据...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded bg-blue-50 p-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="16" className="text-blue-600" />
|
||||||
|
<span className="text-sm text-blue-800">系统正在处理您的请求,请稍候...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading States with Different Speeds
|
||||||
|
export const LoadingStatesWithDifferentSpeeds: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">不同速度的加载动画</h3>
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="24" className="animate-spin" style={{ animationDuration: '2s' }} />
|
||||||
|
<span className="text-xs text-gray-600">慢速 (2s)</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="24" />
|
||||||
|
<span className="text-xs text-gray-600">默认速度</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<SvgSpinners180Ring size="24" className="animate-spin" style={{ animationDuration: '0.5s' }} />
|
||||||
|
<span className="text-xs text-gray-600">快速 (0.5s)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full Page Loading
|
||||||
|
export const FullPageLoading: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">全屏加载示例</h3>
|
||||||
|
<div className="relative h-64 w-full overflow-hidden rounded-lg border border-gray-200 bg-white">
|
||||||
|
<div className="absolute inset-0 flex flex-col items-center justify-center bg-white/80">
|
||||||
|
<SvgSpinners180Ring size="32" className="text-blue-500" />
|
||||||
|
<p className="mt-4 text-sm text-gray-600">页面加载中,请稍候...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 模拟页面内容 */}
|
||||||
|
<div className="p-6 opacity-30">
|
||||||
|
<div className="mb-4 h-6 w-1/3 rounded bg-gray-200"></div>
|
||||||
|
<div className="mb-2 h-4 w-full rounded bg-gray-200"></div>
|
||||||
|
<div className="mb-2 h-4 w-5/6 rounded bg-gray-200"></div>
|
||||||
|
<div className="mb-4 h-4 w-4/6 rounded bg-gray-200"></div>
|
||||||
|
<div className="mb-2 h-4 w-full rounded bg-gray-200"></div>
|
||||||
|
<div className="h-4 w-3/4 rounded bg-gray-200"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interactive Loading Demo
|
||||||
|
export const InteractiveLoadingDemo: Story = {
|
||||||
|
render: () => {
|
||||||
|
const loadingStates = [
|
||||||
|
{ text: '发送消息', color: 'text-blue-500', bgColor: 'bg-blue-500' },
|
||||||
|
{ text: '上传文件', color: 'text-green-500', bgColor: 'bg-green-500' },
|
||||||
|
{ text: '生成内容', color: 'text-purple-500', bgColor: 'bg-purple-500' },
|
||||||
|
{ text: '搜索结果', color: 'text-orange-500', bgColor: 'bg-orange-500' }
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="mb-3 font-semibold">交互式加载演示</h3>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
{loadingStates.map((state, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
type="button"
|
||||||
|
className={`flex items-center justify-center gap-2 rounded-lg ${state.bgColor} px-4 py-3 text-white transition-all hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2`}
|
||||||
|
onClick={() => {
|
||||||
|
// 演示用途 - 在实际应用中这里会触发真实的加载状态
|
||||||
|
alert(`触发 ${state.text} 加载状态`)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SvgSpinners180Ring size="16" />
|
||||||
|
<span>{state.text}中...</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
点击按钮查看加载状态的交互效果
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,389 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
|
import ToolsCallingIcon from '../../../src/components/icons/ToolsCallingIcon'
|
||||||
|
|
||||||
|
const meta: Meta<typeof ToolsCallingIcon> = {
|
||||||
|
title: 'Icons/ToolsCallingIcon',
|
||||||
|
component: ToolsCallingIcon,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered'
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
className: {
|
||||||
|
description: '容器的自定义 CSS 类名',
|
||||||
|
control: { type: 'text' }
|
||||||
|
},
|
||||||
|
iconClassName: {
|
||||||
|
description: '图标的自定义 CSS 类名',
|
||||||
|
control: { type: 'text' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
|
// Basic Tools Calling Icon
|
||||||
|
export const BasicToolsCallingIcon: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">基础工具调用图标</h3>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<ToolsCallingIcon />
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-sm text-gray-600">
|
||||||
|
悬停图标查看工具提示,显示"函数调用"文本
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different Sizes
|
||||||
|
export const DifferentSizes: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">不同尺寸的工具调用图标</h3>
|
||||||
|
<div className="flex items-end gap-6">
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-3 h-3" />
|
||||||
|
<span className="text-xs text-gray-600">小号</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ToolsCallingIcon />
|
||||||
|
<span className="text-xs text-gray-600">默认</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-5 h-5" />
|
||||||
|
<span className="text-xs text-gray-600">中号</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-6 h-6" />
|
||||||
|
<span className="text-xs text-gray-600">大号</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-8 h-8" />
|
||||||
|
<span className="text-xs text-gray-600">特大号</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different Colors
|
||||||
|
export const DifferentColors: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-3 font-semibold">不同颜色的工具调用图标</h3>
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ToolsCallingIcon />
|
||||||
|
<span className="text-xs text-gray-600">默认绿色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-4 h-4 mr-1.5 text-blue-500" />
|
||||||
|
<span className="text-xs text-gray-600">蓝色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-4 h-4 mr-1.5 text-orange-500" />
|
||||||
|
<span className="text-xs text-gray-600">橙色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-4 h-4 mr-1.5 text-red-500" />
|
||||||
|
<span className="text-xs text-gray-600">红色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-4 h-4 mr-1.5 text-purple-500" />
|
||||||
|
<span className="text-xs text-gray-600">紫色</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-4 h-4 mr-1.5 text-gray-500" />
|
||||||
|
<span className="text-xs text-gray-600">灰色</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model Features Context
|
||||||
|
export const ModelFeaturesContext: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="mb-3 font-semibold">在模型功能标识中的使用</h3>
|
||||||
|
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<div className="rounded-lg border border-gray-200 p-4">
|
||||||
|
<div className="mb-2 flex items-center gap-2">
|
||||||
|
<h4 className="font-medium">GPT-4 Turbo</h4>
|
||||||
|
<ToolsCallingIcon />
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
支持函数调用的先进模型,可以调用外部工具和API
|
||||||
|
</p>
|
||||||
|
<div className="mt-2 flex gap-2">
|
||||||
|
<span className="rounded bg-green-100 px-2 py-1 text-xs text-green-800">函数调用</span>
|
||||||
|
<span className="rounded bg-blue-100 px-2 py-1 text-xs text-blue-800">多模态</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-gray-200 p-4">
|
||||||
|
<div className="mb-2 flex items-center gap-2">
|
||||||
|
<h4 className="font-medium">Claude 3.5 Sonnet</h4>
|
||||||
|
<ToolsCallingIcon />
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Anthropic的高性能模型,具备强大的工具使用能力
|
||||||
|
</p>
|
||||||
|
<div className="mt-2 flex gap-2">
|
||||||
|
<span className="rounded bg-green-100 px-2 py-1 text-xs text-green-800">函数调用</span>
|
||||||
|
<span className="rounded bg-orange-100 px-2 py-1 text-xs text-orange-800">推理</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-gray-200 p-4">
|
||||||
|
<div className="mb-2 flex items-center gap-2">
|
||||||
|
<h4 className="font-medium">Llama 3.1 8B</h4>
|
||||||
|
{/* 不支持函数调用 */}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Meta的开源模型,适用于基础对话任务
|
||||||
|
</p>
|
||||||
|
<div className="mt-2 flex gap-2">
|
||||||
|
<span className="rounded bg-gray-100 px-2 py-1 text-xs text-gray-800">文本生成</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat Message Context
|
||||||
|
export const ChatMessageContext: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="mb-3 font-semibold">在聊天消息中的使用</h3>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="rounded-lg bg-blue-50 p-3">
|
||||||
|
<div className="mb-1 flex items-center gap-2 text-sm text-blue-800">
|
||||||
|
<ToolsCallingIcon iconClassName="w-3.5 h-3.5 mr-1 text-[#00b96b]" />
|
||||||
|
<span className="font-medium">调用工具: weather_api</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-blue-700">
|
||||||
|
正在获取北京的天气信息...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg bg-green-50 p-3">
|
||||||
|
<div className="mb-1 flex items-center gap-2 text-sm text-green-800">
|
||||||
|
<ToolsCallingIcon iconClassName="w-3.5 h-3.5 mr-1 text-[#00b96b]" />
|
||||||
|
<span className="font-medium">调用工具: search_web</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-green-700">
|
||||||
|
正在搜索最新的AI新闻...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg bg-orange-50 p-3">
|
||||||
|
<div className="mb-1 flex items-center gap-2 text-sm text-orange-800">
|
||||||
|
<ToolsCallingIcon iconClassName="w-3.5 h-3.5 mr-1 text-[#00b96b]" />
|
||||||
|
<span className="font-medium">调用工具: code_interpreter</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-orange-700">
|
||||||
|
正在执行Python代码计算结果...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tool Availability Indicator
|
||||||
|
export const ToolAvailabilityIndicator: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="mb-3 font-semibold">工具可用性指示器</h3>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-gray-200">
|
||||||
|
<div className="border-b border-gray-200 p-3">
|
||||||
|
<h4 className="font-medium text-gray-900">可用工具</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="divide-y divide-gray-200">
|
||||||
|
<div className="flex items-center justify-between p-3 hover:bg-gray-50">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-4 h-4 mr-1.5 text-[#00b96b]" />
|
||||||
|
<span className="font-medium">天气查询</span>
|
||||||
|
</div>
|
||||||
|
<span className="rounded-full bg-green-100 px-2 py-1 text-xs text-green-800">可用</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between p-3 hover:bg-gray-50">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-4 h-4 mr-1.5 text-[#00b96b]" />
|
||||||
|
<span className="font-medium">网络搜索</span>
|
||||||
|
</div>
|
||||||
|
<span className="rounded-full bg-green-100 px-2 py-1 text-xs text-green-800">可用</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between p-3 hover:bg-gray-50 opacity-60">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-4 h-4 mr-1.5 text-gray-400" />
|
||||||
|
<span className="font-medium">代码执行</span>
|
||||||
|
</div>
|
||||||
|
<span className="rounded-full bg-gray-100 px-2 py-1 text-xs text-gray-800">不可用</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between p-3 hover:bg-gray-50">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-4 h-4 mr-1.5 text-yellow-600" />
|
||||||
|
<span className="font-medium">图像生成</span>
|
||||||
|
</div>
|
||||||
|
<span className="rounded-full bg-yellow-100 px-2 py-1 text-xs text-yellow-800">限制使用</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interactive Tool Selection
|
||||||
|
export const InteractiveToolSelection: Story = {
|
||||||
|
render: () => {
|
||||||
|
const tools = [
|
||||||
|
{ name: '天气查询', description: '获取实时天气信息', available: true },
|
||||||
|
{ name: '网络搜索', description: '搜索最新信息', available: true },
|
||||||
|
{ name: '代码执行', description: '运行Python代码', available: false },
|
||||||
|
{ name: '图像分析', description: '分析和描述图像', available: true },
|
||||||
|
{ name: '数据可视化', description: '创建图表和图形', available: false }
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="mb-3 font-semibold">交互式工具选择器</h3>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-3">
|
||||||
|
{tools.map((tool, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
type="button"
|
||||||
|
className={`flex items-center gap-3 rounded-lg border p-3 text-left transition-all hover:shadow-md focus:outline-none focus:ring-2 focus:ring-blue-500/20 ${
|
||||||
|
tool.available
|
||||||
|
? 'border-gray-200 hover:border-blue-500'
|
||||||
|
: 'border-gray-200 opacity-60 cursor-not-allowed'
|
||||||
|
}`}
|
||||||
|
disabled={!tool.available}
|
||||||
|
>
|
||||||
|
<ToolsCallingIcon
|
||||||
|
iconClassName={`w-4 h-4 mr-1.5 ${
|
||||||
|
tool.available ? 'text-[#00b96b]' : 'text-gray-400'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="font-medium">{tool.name}</div>
|
||||||
|
<div className="text-sm text-gray-600">{tool.description}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs">
|
||||||
|
{tool.available ? (
|
||||||
|
<span className="rounded bg-green-100 px-2 py-1 text-green-800">可用</span>
|
||||||
|
) : (
|
||||||
|
<span className="rounded bg-gray-100 px-2 py-1 text-gray-800">不可用</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading Tool Calls
|
||||||
|
export const LoadingToolCalls: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="mb-3 font-semibold">工具调用加载状态</h3>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="rounded-lg border border-gray-200 p-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ToolsCallingIcon />
|
||||||
|
<span className="font-medium">正在调用工具...</span>
|
||||||
|
<div className="h-2 w-2 animate-pulse rounded-full bg-green-500"></div>
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-sm text-gray-600">weather_api(city="北京")</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-green-200 bg-green-50 p-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-4 h-4 mr-1.5 text-green-600" />
|
||||||
|
<span className="font-medium text-green-800">工具调用完成</span>
|
||||||
|
<span className="text-green-600">✓</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-sm text-green-700">
|
||||||
|
已获取北京天气信息:晴天,温度 22°C
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ToolsCallingIcon iconClassName="w-4 h-4 mr-1.5 text-red-600" />
|
||||||
|
<span className="font-medium text-red-800">工具调用失败</span>
|
||||||
|
<span className="text-red-600">✗</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-1 text-sm text-red-700">
|
||||||
|
API密钥无效,请检查配置
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings Panel
|
||||||
|
export const SettingsPanel: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="mb-3 font-semibold">设置面板中的使用</h3>
|
||||||
|
|
||||||
|
<div className="rounded-lg border border-gray-200 p-4">
|
||||||
|
<div className="mb-4 flex items-center gap-2">
|
||||||
|
<ToolsCallingIcon />
|
||||||
|
<h4 className="font-medium">函数调用设置</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">启用函数调用</div>
|
||||||
|
<div className="text-sm text-gray-600">允许AI模型调用外部工具</div>
|
||||||
|
</div>
|
||||||
|
<input type="checkbox" className="rounded" defaultChecked />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">自动确认调用</div>
|
||||||
|
<div className="text-sm text-gray-600">自动执行安全的工具调用</div>
|
||||||
|
</div>
|
||||||
|
<input type="checkbox" className="rounded" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">显示调用详情</div>
|
||||||
|
<div className="text-sm text-gray-600">在聊天中显示工具调用过程</div>
|
||||||
|
</div>
|
||||||
|
<input type="checkbox" className="rounded" defaultChecked />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import CustomCollapse from '@renderer/components/CustomCollapse'
|
import { CustomCollapse } from '@cherrystudio/ui'
|
||||||
import { DynamicVirtualList, type DynamicVirtualListRef } from '@renderer/components/VirtualList'
|
import { DynamicVirtualList, type DynamicVirtualListRef } from '@renderer/components/VirtualList'
|
||||||
import type { Model } from '@renderer/types'
|
import type { Model } from '@renderer/types'
|
||||||
import type { ModelWithStatus } from '@renderer/types/healthCheck'
|
import type { ModelWithStatus } from '@renderer/types/healthCheck'
|
||||||
@ -47,6 +47,7 @@ const ModelListGroup: React.FC<ModelListGroupProps> = ({
|
|||||||
return (
|
return (
|
||||||
<CustomCollapseWrapper>
|
<CustomCollapseWrapper>
|
||||||
<CustomCollapse
|
<CustomCollapse
|
||||||
|
variant="shadow"
|
||||||
defaultActiveKey={defaultOpen ? ['1'] : []}
|
defaultActiveKey={defaultOpen ? ['1'] : []}
|
||||||
onChange={handleCollapseChange}
|
onChange={handleCollapseChange}
|
||||||
label={
|
label={
|
||||||
@ -69,9 +70,7 @@ const ModelListGroup: React.FC<ModelListGroupProps> = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
styles={{
|
styles={{
|
||||||
header: {
|
trigger: 'p-[3px_calc(6px_+_var(--scrollbar-width))_3px_16px]'
|
||||||
padding: '3px calc(6px + var(--scrollbar-width)) 3px 16px'
|
|
||||||
}
|
|
||||||
}}>
|
}}>
|
||||||
<DynamicVirtualList
|
<DynamicVirtualList
|
||||||
ref={listRef}
|
ref={listRef}
|
||||||
@ -115,7 +114,8 @@ const CustomCollapseWrapper = styled.div`
|
|||||||
/* 移除 collapse 的 padding,转而在 scroller 内部调整 */
|
/* 移除 collapse 的 padding,转而在 scroller 内部调整 */
|
||||||
.ant-collapse-content-box {
|
.ant-collapse-content-box {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}import { classNames } from '../../../../utils/style';
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
export default memo(ModelListGroup)
|
export default memo(ModelListGroup)
|
||||||
|
|||||||
12
yarn.lock
12
yarn.lock
@ -2653,6 +2653,7 @@ __metadata:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@heroui/react": "npm:^2.8.4"
|
"@heroui/react": "npm:^2.8.4"
|
||||||
"@storybook/addon-docs": "npm:^9.1.6"
|
"@storybook/addon-docs": "npm:^9.1.6"
|
||||||
|
"@storybook/addon-themes": "npm:^9.1.6"
|
||||||
"@storybook/react-vite": "npm:^9.1.6"
|
"@storybook/react-vite": "npm:^9.1.6"
|
||||||
"@types/react": "npm:^19.0.12"
|
"@types/react": "npm:^19.0.12"
|
||||||
"@types/react-dom": "npm:^19.0.4"
|
"@types/react-dom": "npm:^19.0.4"
|
||||||
@ -11842,6 +11843,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@storybook/addon-themes@npm:^9.1.6":
|
||||||
|
version: 9.1.6
|
||||||
|
resolution: "@storybook/addon-themes@npm:9.1.6"
|
||||||
|
dependencies:
|
||||||
|
ts-dedent: "npm:^2.0.0"
|
||||||
|
peerDependencies:
|
||||||
|
storybook: ^9.1.6
|
||||||
|
checksum: 10c0/3d5e17e19af70aee021537fcbf8782558de9abd85d96d813f66a510e217a58d1312af3e7aaf926f0f33eabf93d3ef2133581dd7e1f5fc28a2c5693ae7f33b5aa
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@storybook/builder-vite@npm:9.1.6":
|
"@storybook/builder-vite@npm:9.1.6":
|
||||||
version: 9.1.6
|
version: 9.1.6
|
||||||
resolution: "@storybook/builder-vite@npm:9.1.6"
|
resolution: "@storybook/builder-vite@npm:9.1.6"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user