dishui 0.0.72 → 0.0.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Button/index.d.ts +4 -4
- package/dist/components/Button/index.js +18 -17
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/Card/Metric.d.ts +17 -0
- package/dist/components/Card/Metric2.js +50 -0
- package/dist/components/Card/Metric2.js.map +1 -0
- package/dist/components/Card/index.d.ts +3 -0
- package/dist/components/Card/index.js +118 -117
- package/dist/components/Card/index.js.map +1 -1
- package/dist/components/Group/index.d.ts +2 -0
- package/dist/components/GroupButton/index.d.ts +16 -0
- package/dist/components/GroupButton/index.js +41 -0
- package/dist/components/GroupButton/index.js.map +1 -0
- package/dist/components/GroupedTable/index.d.ts +38 -0
- package/dist/components/GroupedTable/index.js +92 -0
- package/dist/components/GroupedTable/index.js.map +1 -0
- package/dist/components/Layout/index.d.ts +4 -2
- package/dist/components/Layout/index.js +1 -1
- package/dist/components/Layout/index.js.map +1 -1
- package/dist/components/NavigationList/index.d.ts +17 -0
- package/dist/components/NavigationList/index.js +48 -0
- package/dist/components/NavigationList/index.js.map +1 -0
- package/dist/components/Section/index.d.ts +28 -0
- package/dist/components/Section/index.js +58 -0
- package/dist/components/Section/index.js.map +1 -0
- package/dist/components/Select/index.d.ts +2 -0
- package/dist/components/Select/index.js +83 -82
- package/dist/components/Select/index.js.map +1 -1
- package/dist/components/SidebarToggleButton/index.d.ts +16 -1
- package/dist/components/SidebarToggleButton/index.js +90 -30
- package/dist/components/SidebarToggleButton/index.js.map +1 -1
- package/dist/components/Skeleton/index.d.ts +7 -0
- package/dist/components/Skeleton/index.js +21 -0
- package/dist/components/Skeleton/index.js.map +1 -0
- package/dist/components/Tree/Tree.js +5 -2
- package/dist/components/Tree/Tree.js.map +1 -1
- package/dist/components/pro/RichTextEditor/AIRewrite.js +114 -72
- package/dist/components/pro/RichTextEditor/AIRewrite.js.map +1 -1
- package/dist/components/pro/RichTextEditor/BlockHandle.js +315 -196
- package/dist/components/pro/RichTextEditor/BlockHandle.js.map +1 -1
- package/dist/components/pro/RichTextEditor/BubbleToolbar.d.ts +1 -1
- package/dist/components/pro/RichTextEditor/BubbleToolbar.js +69 -33
- package/dist/components/pro/RichTextEditor/BubbleToolbar.js.map +1 -1
- package/dist/components/pro/RichTextEditor/CommentPanel.d.ts +2 -1
- package/dist/components/pro/RichTextEditor/CommentPanel.js +100 -81
- package/dist/components/pro/RichTextEditor/CommentPanel.js.map +1 -1
- package/dist/components/pro/RichTextEditor/CommentPopover.d.ts +2 -1
- package/dist/components/pro/RichTextEditor/CommentPopover.js +92 -68
- package/dist/components/pro/RichTextEditor/CommentPopover.js.map +1 -1
- package/dist/components/pro/RichTextEditor/RichTextEditor.js +24 -22
- package/dist/components/pro/RichTextEditor/RichTextEditor.js.map +1 -1
- package/dist/components/pro/RichTextEditor/SearchReplace.d.ts +7 -0
- package/dist/components/pro/RichTextEditor/SlashMenu.js +136 -73
- package/dist/components/pro/RichTextEditor/SlashMenu.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/Toolbar.d.ts +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/Toolbar.js +346 -313
- package/dist/components/pro/RichTextEditor/Toolbar/Toolbar.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/CodeBlockView.js +114 -146
- package/dist/components/pro/RichTextEditor/extensions/CodeBlockView.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/CommentMark.js +2 -2
- package/dist/components/pro/RichTextEditor/extensions/CommentMark.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/FloatingDialog.js +178 -163
- package/dist/components/pro/RichTextEditor/extensions/FloatingDialog.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/FontSize.js +8 -5
- package/dist/components/pro/RichTextEditor/extensions/FontSize.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/MarkdownBlock.d.ts +6 -0
- package/dist/components/pro/RichTextEditor/extensions/MarkdownBlock.js +85 -74
- package/dist/components/pro/RichTextEditor/extensions/MarkdownBlock.js.map +1 -1
- package/dist/components/pro/RichTextEditor/searchUtils.d.ts +23 -0
- package/dist/contexts/AppContext.js +47 -45
- package/dist/contexts/AppContext.js.map +1 -1
- package/dist/dishui.css +1 -1
- package/dist/index-nostyled.d.ts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +119 -114
- package/dist/layouts/MainLayout.js +9 -10
- package/dist/layouts/MainLayout.js.map +1 -1
- package/dist/layouts/Sidebar.d.ts +9 -0
- package/dist/layouts/Sidebar.js +99 -80
- package/dist/layouts/Sidebar.js.map +1 -1
- package/dist/layouts/headbar/Headbar.d.ts +1 -1
- package/dist/layouts/headbar/Headbar.js +2 -2
- package/dist/layouts/headbar/Headbar.js.map +1 -1
- package/dist/lite.d.ts +19 -1
- package/dist/lite.js +17 -8
- package/dist/styles/themeStyles.js +6 -6
- package/dist/styles/themeStyles.js.map +1 -1
- package/dist/utils/i18n.d.ts +3 -0
- package/dist/utils/i18n.js +78 -0
- package/dist/utils/i18n.js.map +1 -1
- package/dist/utils/menu.js +23 -6
- package/dist/utils/menu.js.map +1 -1
- package/package.json +8 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Card/index.tsx"],"sourcesContent":["import React from 'react';\nimport { cn } from '../../utils/cn';\n\nexport type CardProps = {\n children: React.ReactNode;\n variant?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error';\n bordered?: boolean;\n dash?: boolean;\n side?: boolean;\n imageFull?: boolean;\n compact?: boolean;\n hover?: boolean;\n glass?: boolean; // 毛玻璃效果\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n className?: string;\n};\n\nconst Card = ({\n children,\n variant,\n bordered = false,\n dash = false,\n side = false,\n imageFull = false,\n compact = false,\n hover = true, // 默认启用 hover 效果\n glass = true, // 默认启用毛玻璃效果\n size = 'sm',\n className = '',\n}: CardProps) => {\n const baseClass = 'card';\n\n // 默认样式:如果没有指定 variant,则应用毛玻璃效果和阴影\n // 同时添加边界样式 (border + shadow)\n const defaultStyles = glass && !variant ? 'glass shadow-lg' : '';\n\n // 如果指定了 variant,使用该 variant 的背景色,并添加阴影\n const variantClass = variant ? `bg-${variant} text-${variant}-content shadow-md` : '';\n\n const borderedClass = bordered ? 'card-bordered' : '';\n const dashClass = dash ? 'card-dash' : '';\n const sideClass = side ? 'card-side' : '';\n const imageFullClass = imageFull ? 'image-full' : '';\n const compactClass = compact ? 'card-compact' : '';\n const hoverClass = hover ? 'hover:shadow-xl transition-shadow duration-300' : '';\n const sizeClass = size ? `card-${size}` : '';\n\n // 宽度策略:\n // 1. 如果用户指定了 w-/max-w-/min-w-,则完全使用用户的设置\n // 2. 否则不设置固定宽度,让 Card 自然适应父容器(Grid 中自动填充,Flex 中根据内容)\n const hasWidthClass = className.includes('w-') || className.match(/max-w-|min-w-/);\n const widthClass = ''; // 不设置默认宽度,让布局决定\n\n const cardClasses = cn(\n baseClass,\n widthClass,\n defaultStyles,\n variantClass,\n borderedClass,\n dashClass,\n sideClass,\n imageFullClass,\n compactClass,\n hoverClass,\n sizeClass,\n className\n );\n\n return (\n <div className={cardClasses}>\n {children}\n </div>\n );\n};\n\n// Card.Title 组件\nexport type CardTitleProps = {\n children: React.ReactNode;\n className?: string;\n};\n\nconst CardTitle = ({\n children,\n className = '',\n}: CardTitleProps) => {\n const titleClasses = cn('card-title', className);\n\n return (\n <h2 className={titleClasses}>\n {children}\n </h2>\n );\n};\n\n// Card.Body 组件\nexport type CardBodyProps = {\n children: React.ReactNode;\n className?: string;\n};\n\nconst CardBody = ({\n children,\n className = '',\n}: CardBodyProps) => {\n const bodyClasses = cn('card-body', className);\n\n return (\n <div className={bodyClasses}>\n {children}\n </div>\n );\n};\n\n// Card.Actions 组件\nexport type CardActionsProps = {\n children: React.ReactNode;\n className?: string;\n justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';\n};\n\nconst CardActions = ({\n children,\n className = '',\n justify = 'end',\n}: CardActionsProps) => {\n const actionsClasses = cn(\n 'card-actions',\n justify && `justify-${justify}`,\n className\n );\n\n return (\n <div className={actionsClasses}>\n {children}\n </div>\n );\n};\n\n// Card.Figure 组件\nexport type CardFigureProps = {\n children: React.ReactNode;\n className?: string;\n /**\n * 图片填充方式(作用于内部 <img>):\n * - `cover` 等比裁切填满(默认,“撑满”)\n * - `contain` 等比完整展示,可能留白\n * - `fill` 拉伸填满(不保持比例)\n */\n fit?: 'cover' | 'contain' | 'fill';\n /** 图片撑满整个 figure(w/h 100%)。card-side 时建议开启。 */\n fill?: boolean;\n /** 指定 figure 高度(数字按 px,支持 \"16rem\"/\"50%\" 等);内部图片自动填满该高度 */\n height?: number | string;\n};\n\nconst FIT_CLASS: Record<NonNullable<CardFigureProps['fit']>, string> = {\n cover: '[&>img]:object-cover',\n contain: '[&>img]:object-contain',\n fill: '[&>img]:object-fill',\n};\n\nconst toCssSize = (value: number | string | undefined): string | undefined =>\n typeof value === 'number' ? `${value}px` : value;\n\nconst CardFigure = ({\n children,\n className = '',\n fit = 'cover',\n fill = false,\n height,\n}: CardFigureProps) => {\n const figureClasses = cn(\n 'figure',\n FIT_CLASS[fit],\n // 撑满:figure 与内部 img 都占满可用空间\n fill && 'w-full h-full [&>img]:h-full [&>img]:w-full',\n // 指定高度:figure 定高,内部图片自动填满\n height !== undefined && 'w-full [&>img]:h-full [&>img]:w-full',\n className\n );\n\n return (\n <figure className={figureClasses} style={height !== undefined ? { height: toCssSize(height) } : undefined}>\n {children}\n </figure>\n );\n};\n\n// ─── 内部工具:复用基础 Card 的类名拼装逻辑 ──────────────────\n\ninterface CardClassOptions {\n variant?: CardProps['variant'];\n bordered?: boolean;\n glass?: boolean;\n className?: string;\n}\n\nconst buildCardClasses = ({ variant, bordered = false, glass = true, className = '' }: CardClassOptions) => {\n const defaultStyles = glass && !variant ? 'glass shadow-lg' : '';\n const variantClass = variant ? `bg-${variant} text-${variant}-content shadow-md` : '';\n return cn('card', defaultStyles, variantClass, bordered && 'card-bordered', className);\n};\n\nconst ClockGlyph: React.FC<{ className?: string }> = ({ className }) => (\n <svg xmlns=\"http://www.w3.org/2000/svg\" className={cn('h-4 w-4', className)} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path strokeLinecap=\"round\" d=\"M12 7v5l3 2\" />\n </svg>\n);\n\n// ─── Card.Feature:特性卡(图标 + 标题 + 描述) ───────────────\n\nexport type CardFeatureProps = {\n /** Icon node (usually a lucide / inline svg), rendered inside a tinted container. */\n icon?: React.ReactNode;\n /** Feature title. */\n title: React.ReactNode;\n /** Feature description rendered under the title. */\n description?: React.ReactNode;\n /** `vertical` centers everything with the icon on top; `horizontal` puts the icon at the left. */\n layout?: 'vertical' | 'horizontal';\n /** Icon container shape. Defaults to `circle` (vertical) / `square` (horizontal). */\n iconShape?: 'circle' | 'square';\n /** Extra content rendered under the description. */\n children?: React.ReactNode;\n /** Color variant (daisyUI). */\n variant?: CardProps['variant'];\n /** Show a border. */\n bordered?: boolean;\n /** Disable the default glass background. */\n glass?: boolean;\n /** Extra class names on the card. */\n className?: string;\n /** Click handler. */\n onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nconst CardFeature: React.FC<CardFeatureProps> = ({\n icon,\n title,\n description,\n layout = 'vertical',\n iconShape,\n children,\n variant,\n bordered,\n glass = true,\n className = '',\n onClick,\n}) => {\n const shape = iconShape ?? (layout === 'vertical' ? 'circle' : 'square');\n const iconBox = icon && (\n <span\n className={cn(\n 'flex shrink-0 items-center justify-center bg-primary/10 text-primary',\n layout === 'vertical' ? 'size-16' : 'size-12',\n shape === 'circle' ? 'rounded-full' : 'rounded-xl',\n )}\n >\n {icon}\n </span>\n );\n\n return (\n <div\n className={buildCardClasses({ variant, bordered, glass, className })}\n onClick={onClick}\n >\n {layout === 'horizontal' ? (\n <div className=\"card-body flex-row items-center gap-4\">\n {iconBox}\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"font-semibold text-base-content\">{title}</h3>\n {description && (\n <p className=\"mt-0.5 text-sm text-base-content/70\">{description}</p>\n )}\n {children}\n </div>\n </div>\n ) : (\n <div className=\"card-body items-center gap-3 text-center\">\n {iconBox}\n <h3 className=\"text-lg font-bold text-base-content\">{title}</h3>\n {description && (\n <p className=\"text-sm leading-relaxed text-base-content/70\">{description}</p>\n )}\n {children}\n </div>\n )}\n </div>\n );\n};\n\n// ─── Card.Quote:引用卡(引用文本 + 作者 / 日期 + 装饰引号) ──\n\nexport type CardQuoteProps = {\n /** The quoted text. */\n children: React.ReactNode;\n /** Author name (rendered in primary color). */\n author?: React.ReactNode;\n /** Avatar node (e.g. an `<img>` or initial circle) shown before the author. */\n avatar?: React.ReactNode;\n /** Date string/node rendered at the right side of the footer. */\n date?: React.ReactNode;\n /** Hide the decorative quote glyph in the top-right corner. */\n quoteMark?: boolean;\n /** Hide the divider between quote and footer. */\n divider?: boolean;\n /** Color variant (daisyUI). */\n variant?: CardProps['variant'];\n /** Show a border. */\n bordered?: boolean;\n /** Disable the default glass background. */\n glass?: boolean;\n /** Extra class names on the card. */\n className?: string;\n /** Click handler. */\n onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nconst CardQuote: React.FC<CardQuoteProps> = ({\n children,\n author,\n avatar,\n date,\n quoteMark = true,\n divider = true,\n variant,\n bordered,\n glass = true,\n className = '',\n onClick,\n}) => (\n <div\n className={cn(\n buildCardClasses({ variant, bordered, glass, className }),\n 'relative overflow-hidden',\n )}\n onClick={onClick}\n >\n {quoteMark && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute right-5 top-2 select-none font-serif text-6xl leading-none text-primary/25\"\n >\n ”\n </span>\n )}\n <div className=\"card-body gap-0 p-6\">\n <div className=\"relative z-10 text-lg text-base-content\">{children}</div>\n <div\n className={cn(\n 'mt-4 flex items-center justify-between gap-3',\n divider && 'border-t border-base-300 pt-3.5',\n )}\n >\n <div className=\"flex min-w-0 items-center gap-2.5\">\n {avatar && <span className=\"size-8 shrink-0 overflow-hidden rounded-full\">{avatar}</span>}\n {author && (\n <span className=\"truncate text-sm font-medium text-primary\">{author}</span>\n )}\n </div>\n {date && (\n <span className=\"flex shrink-0 items-center gap-1.5 text-sm text-base-content/55\">\n <ClockGlyph />\n {date}\n </span>\n )}\n </div>\n </div>\n </div>\n);\n\n// ─── Card.Meta:元信息卡(标题 + 徽章 + 描述 + 左右页脚) ────\n\nexport type CardMetaProps = {\n /** Card title (top-left). */\n title: React.ReactNode;\n /** Badge / tag node (top-right, e.g. a visibility badge). */\n badge?: React.ReactNode;\n /** Body content under the title row. */\n children?: React.ReactNode;\n /** Footer left slot (e.g. `@author`). */\n footerLeft?: React.ReactNode;\n /** Footer right slot (e.g. a date). */\n footerRight?: React.ReactNode;\n /** Color variant (daisyUI). */\n variant?: CardProps['variant'];\n /** Show a border. */\n bordered?: boolean;\n /** Disable the default glass background. */\n glass?: boolean;\n /** Extra class names on the card. */\n className?: string;\n /** Click handler. */\n onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nconst CardMeta: React.FC<CardMetaProps> = ({\n title,\n badge,\n children,\n footerLeft,\n footerRight,\n variant,\n bordered,\n glass = true,\n className = '',\n onClick,\n}) => (\n <div\n className={buildCardClasses({ variant, bordered, glass, className })}\n onClick={onClick}\n >\n <div className=\"card-body gap-2.5 p-6\">\n <div className=\"flex items-center justify-between gap-3\">\n <h3 className=\"min-w-0 flex-1 truncate text-lg font-bold text-base-content\">{title}</h3>\n {badge && <span className=\"shrink-0\">{badge}</span>}\n </div>\n {children && <div className=\"text-sm text-base-content/75\">{children}</div>}\n {(footerLeft || footerRight) && (\n <div className=\"mt-1.5 flex items-center justify-between gap-3 text-sm text-base-content/55\">\n <span className=\"min-w-0 truncate\">{footerLeft}</span>\n <span className=\"shrink-0\">{footerRight}</span>\n </div>\n )}\n </div>\n </div>\n);\n\n// ─── Card.Info:指标卡(标签 + 数值 + 可选图标 / hint / 加载态) ──\n\nexport type CardInfoTone = 'primary' | 'success' | 'warning' | 'info' | 'error' | 'neutral';\n\nexport type CardInfoProps = {\n /** Metric name (top-left, small muted text). */\n label: React.ReactNode;\n /** Metric value (number or short text). */\n value: React.ReactNode;\n /** Icon node, auto-sized and tinted by tone. */\n icon?: React.ReactNode;\n /** `top` (default) puts the icon at the right of the label row; `left` puts it on the left (horizontal layout). */\n iconPosition?: 'top' | 'left';\n /** 图标底框形状:`square`(默认,方形圆角)/ `circle`(正圆)。 */\n iconShape?: 'square' | 'circle';\n /** 是否给图标加色调底框(默认 `true`);`false` 时渲染不带底框、仅保留 tone 文字色的裸图标。 */\n iconBoxed?: boolean;\n /** Tone (icon text & background color). */\n tone?: CardInfoTone;\n /** Small hint text below the value (e.g. deltas, update time). */\n hint?: React.ReactNode;\n /** Loading state: value renders as a skeleton. */\n loading?: boolean;\n /** Color variant (daisyUI). */\n variant?: CardProps['variant'];\n /** Show a border (default true). */\n bordered?: boolean;\n /** Enable the glass background (default false — solid `bg-base-200`). */\n glass?: boolean;\n /** Extra class names on the card. */\n className?: string;\n /** Click handler. */\n onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nconst INFO_TONE_CLASS: Record<NonNullable<CardInfoProps['tone']>, { text: string; bg: string }> = {\n primary: { text: 'text-primary', bg: 'bg-primary/10' },\n success: { text: 'text-success', bg: 'bg-success/10' },\n warning: { text: 'text-warning', bg: 'bg-warning/10' },\n info: { text: 'text-info', bg: 'bg-info/10' },\n error: { text: 'text-error', bg: 'bg-error/10' },\n neutral: { text: 'text-base-content/70', bg: 'bg-base-content/5' },\n};\n\nconst CardInfo: React.FC<CardInfoProps> = ({\n label,\n value,\n icon,\n iconPosition = 'top',\n iconShape = 'square',\n iconBoxed = true,\n tone = 'primary',\n hint,\n loading = false,\n variant,\n bordered = true,\n glass = false,\n className = '',\n onClick,\n}) => {\n const t = INFO_TONE_CLASS[tone];\n const cardClasses = buildCardClasses({ variant, bordered, glass, className: cn('bg-base-200 p-4', className) });\n\n const iconShapeClass = iconShape === 'circle' ? 'rounded-full' : iconPosition === 'left' ? 'rounded-lg' : 'rounded-md';\n\n // 带色调底框的图标容器\n const boxedIcon = icon && (\n <span\n className={cn(\n iconPosition === 'left'\n ? 'flex shrink-0 items-center justify-center p-2.5 [&>svg]:h-5 [&>svg]:w-5'\n : 'p-1.5 [&>svg]:h-4 [&>svg]:w-4',\n iconShapeClass,\n t.bg,\n t.text,\n )}\n >\n {icon}\n </span>\n );\n\n // 裸图标:无底框,仅 tone 文字色\n const bareIcon = icon && (\n <span\n className={cn(\n iconPosition === 'left' ? 'flex shrink-0' : 'inline-flex',\n '[&>svg]:h-6 [&>svg]:w-6',\n t.text,\n )}\n >\n {icon}\n </span>\n );\n\n // 图标在左、文字居右的水平布局\n if (iconPosition === 'left') {\n return (\n <div\n className={cn(cardClasses, 'transition-shadow duration-300 hover:shadow-xl')}\n onClick={onClick}\n >\n <div className=\"flex items-center gap-3\">\n {iconBoxed ? boxedIcon : bareIcon}\n <div className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-xs font-medium text-base-content/60\">{label}</span>\n {loading ? (\n <div className=\"mt-1 h-6 w-14 animate-pulse rounded bg-base-content/10\" />\n ) : (\n <p className=\"truncate text-xl font-bold leading-tight text-base-content\">{value}</p>\n )}\n {hint && <p className=\"mt-0.5 truncate text-[11px] text-base-content/50\">{hint}</p>}\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div\n className={cn(cardClasses, 'transition-shadow duration-300 hover:shadow-xl')}\n onClick={onClick}\n >\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <span className=\"truncate text-xs font-medium text-base-content/60\">{label}</span>\n {iconBoxed ? boxedIcon : bareIcon}\n </div>\n {loading ? (\n <div className=\"h-7 w-14 animate-pulse rounded bg-base-content/10\" />\n ) : (\n <p className=\"text-2xl font-bold leading-none text-base-content\">{value}</p>\n )}\n {hint && <p className=\"mt-1.5 text-[11px] text-base-content/50\">{hint}</p>}\n </div>\n );\n};\n\n// ─── Card.Stat:统计面板(Grafana 风格:超大数值 + 迷你图 / 角标 / 填充色) ───\n\nexport type CardStatProps = {\n /** 面板标签 */\n label: React.ReactNode;\n /** 面板数值(数字或短文本) */\n value: React.ReactNode;\n /** 色调(决定数值文字色 / 角标三角 / filled 底色) */\n tone?: CardInfoTone;\n /** 加载态:值显示为骨架 */\n loading?: boolean;\n className?: string;\n /** 数值单位后缀(如 `K`、`%`),以小号字体渲染在数值右侧。 */\n unit?: React.ReactNode;\n /** 面板数值字号,默认 `xl`。 */\n valueSize?: 'md' | 'lg' | 'xl' | '2xl';\n /** 面板内容对齐,默认 `left`。 */\n align?: 'left' | 'center';\n /** 直接传入迷你图节点(优先于 sparklineData)。 */\n sparkline?: React.ReactNode;\n /** 迷你图数据:传入数值数组后自动渲染 sparkline。 */\n sparklineData?: number[];\n /** 迷你图类型:折线 / 面积 / 柱状。 */\n sparklineType?: 'line' | 'area' | 'bar';\n /** 迷你图高度(px)。 */\n sparklineHeight?: number;\n /** 右上角状态三角(Grafana 阈值指示)。 */\n indicator?: boolean;\n /** 整卡填充 tone 底色、文字用对应 content 色(图 RabbitMQ 风格)。 */\n filled?: boolean;\n /** 点击回调(面板常用于跳转详情)。 */\n onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nconst STAT_TONE_TEXT_CLASS: Record<NonNullable<CardStatProps['tone']>, string> = {\n primary: 'text-primary',\n success: 'text-success',\n warning: 'text-warning',\n info: 'text-info',\n error: 'text-error',\n neutral: 'text-base-content/70',\n};\n\n/** filled 模式:整卡 tone 底色 + 对应 content 文字色 */\nconst STAT_FILLED_CLASS: Record<NonNullable<CardStatProps['tone']>, string> = {\n primary: 'bg-primary text-primary-content',\n success: 'bg-success text-success-content',\n warning: 'bg-warning text-warning-content',\n info: 'bg-info text-info-content',\n error: 'bg-error text-error-content',\n neutral: 'bg-neutral text-neutral-content',\n};\n\nconst STAT_VALUE_SIZE_CLASS: Record<NonNullable<CardStatProps['valueSize']> & string, string> = {\n md: 'text-2xl',\n lg: 'text-4xl',\n xl: 'text-5xl',\n '2xl': 'text-6xl',\n};\n\nconst statCssVarColor = (tone: NonNullable<CardStatProps['tone']>) =>\n tone === 'neutral' ? 'var(--color-base-content)' : `var(--color-${tone})`;\n\n/** 由数值数组生成迷你图(viewBox 拉伸铺满,描边不随宽度变形) */\nconst MiniSparkline: React.FC<{\n data: number[];\n type: 'line' | 'area' | 'bar';\n height: number;\n}> = ({ data, type, height }) => {\n if (!data || data.length < 2) return null;\n const w = 100;\n const pad = 3;\n const min = Math.min(...data);\n const max = Math.max(...data);\n const range = max - min || 1;\n const px = (i: number) => (i / (data.length - 1)) * w;\n const py = (v: number) => height - pad - ((v - min) / range) * (height - pad * 2);\n const points = data.map((v, i) => `${px(i).toFixed(2)},${py(v).toFixed(2)}`);\n\n if (type === 'bar') {\n const bw = w / data.length;\n return (\n <svg\n viewBox={`0 0 ${w} ${height}`}\n preserveAspectRatio=\"none\"\n className=\"block w-full\"\n style={{ height }}\n aria-hidden=\"true\"\n >\n {data.map((v, i) => (\n <rect\n key={i}\n x={px(i) - bw / 2}\n y={py(v)}\n width={bw * 0.8}\n height={Math.max(1, height - pad - py(v))}\n fill=\"currentColor\"\n />\n ))}\n </svg>\n );\n }\n\n if (type === 'area') {\n return (\n <svg\n viewBox={`0 0 ${w} ${height}`}\n preserveAspectRatio=\"none\"\n className=\"block w-full\"\n style={{ height }}\n aria-hidden=\"true\"\n >\n <path\n d={`M0,${height} L${points.join(' L')} L${w},${height} Z`}\n fill=\"currentColor\"\n opacity={0.18}\n />\n <polyline\n points={points.join(' ')}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.5}\n vectorEffect=\"non-scaling-stroke\"\n />\n </svg>\n );\n }\n\n return (\n <svg\n viewBox={`0 0 ${w} ${height}`}\n preserveAspectRatio=\"none\"\n className=\"block w-full\"\n style={{ height }}\n aria-hidden=\"true\"\n >\n <polyline\n points={points.join(' ')}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.5}\n vectorEffect=\"non-scaling-stroke\"\n />\n </svg>\n );\n};\n\n/**\n * 统计面板:Grafana stat panel 风格的「标签 + 超大数值 + 迷你图」小卡,\n * 支持单位后缀、折线 / 面积 / 柱状迷你图、角标状态三角与整卡填充色。\n *\n * 简单的「标签 + 数值 + 图标」指标卡请使用 `Card.Info`。\n */\nconst CardStat: React.FC<CardStatProps> = ({\n label,\n value,\n tone = 'primary',\n loading = false,\n className = '',\n unit,\n valueSize = 'xl',\n align = 'left',\n sparkline,\n sparklineData,\n sparklineType = 'line',\n sparklineHeight = 36,\n indicator = false,\n filled = false,\n onClick,\n}) => {\n const sparklineNode =\n sparkline ??\n (sparklineData && sparklineData.length >= 2 ? (\n <MiniSparkline data={sparklineData} type={sparklineType} height={sparklineHeight} />\n ) : null);\n\n const textColor = filled ? '' : STAT_TONE_TEXT_CLASS[tone];\n const mutedText = filled ? 'opacity-80' : 'text-base-content/60';\n\n return (\n <div\n onClick={onClick}\n className={`relative flex flex-col overflow-hidden rounded-lg p-3 ${\n filled ? STAT_FILLED_CLASS[tone] : 'border border-base-300/60 bg-base-200'\n } ${onClick ? 'cursor-pointer transition-colors hover:border-primary/40' : ''} ${className}`.trim()}\n >\n {indicator && !filled && (\n <span\n aria-hidden=\"true\"\n className=\"absolute right-0 top-0 h-0 w-0\"\n style={{\n borderTop: '14px solid ' + statCssVarColor(tone),\n borderLeft: '14px solid transparent',\n }}\n />\n )}\n <div\n className={`truncate text-xs font-medium ${mutedText} ${align === 'center' ? 'text-center' : ''}`.trim()}\n >\n {label}\n </div>\n <div\n className={`flex min-h-[3rem] flex-1 items-center py-1 ${align === 'center' ? 'justify-center' : 'justify-start'}`}\n >\n {loading ? (\n <span\n className={`inline-block h-8 w-16 animate-pulse rounded ${\n filled ? 'bg-white/25' : 'bg-base-content/10'\n }`}\n />\n ) : (\n <span className={`leading-none tracking-tight ${STAT_VALUE_SIZE_CLASS[valueSize]} font-semibold ${filled ? '' : textColor}`.trim()}>\n {value}\n {unit !== undefined && unit !== null && unit !== '' && (\n <span className=\"ml-0.5 text-[0.45em] font-semibold\">{unit}</span>\n )}\n </span>\n )}\n </div>\n {sparklineNode && (\n <div className={`-mx-3 -mb-3 mt-1 ${filled ? 'text-white' : STAT_TONE_TEXT_CLASS[tone]}`}>\n {sparklineNode}\n </div>\n )}\n </div>\n );\n};\n\n// 扩展Card组件,添加子组件\ninterface CardCompound extends React.FC<CardProps> {\n Title: React.FC<CardTitleProps>;\n Body: React.FC<CardBodyProps>;\n Actions: React.FC<CardActionsProps>;\n Figure: React.FC<CardFigureProps>;\n /** 特性卡:图标 + 标题 + 描述(垂直居中 / 横向两种布局) */\n Feature: React.FC<CardFeatureProps>;\n /** 引用卡:引用文本 + 作者头像 / 日期 + 装饰引号 */\n Quote: React.FC<CardQuoteProps>;\n /** 元信息卡:标题 + 徽章 + 描述 + 左右页脚 */\n Meta: React.FC<CardMetaProps>;\n /** 指标卡:标签 + 数值 + 可选图标 / hint / 加载态(仪表盘统计卡片) */\n Info: React.FC<CardInfoProps>;\n /** 统计面板:Grafana 风格超大数值 + 迷你折线 / 面积 / 柱状图 / 角标 / 填充色 */\n Stat: React.FC<CardStatProps>;\n}\n\nconst CardWithCompounds = Card as CardCompound;\nCardWithCompounds.Title = CardTitle;\nCardWithCompounds.Body = CardBody;\nCardWithCompounds.Actions = CardActions;\nCardWithCompounds.Figure = CardFigure;\nCardWithCompounds.Feature = CardFeature;\nCardWithCompounds.Quote = CardQuote;\nCardWithCompounds.Meta = CardMeta;\nCardWithCompounds.Info = CardInfo;\nCardWithCompounds.Stat = CardStat;\n\nexport default CardWithCompounds; "],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Card/index.tsx"],"sourcesContent":["import React from 'react';\nimport { cn } from '../../utils/cn';\nimport { CardMetric, type CardMetricProps } from './Metric';\nexport type { CardMetricProps, CardMetricItem } from './Metric';\n\nexport type CardProps = {\n children: React.ReactNode;\n variant?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error';\n bordered?: boolean;\n dash?: boolean;\n side?: boolean;\n imageFull?: boolean;\n compact?: boolean;\n hover?: boolean;\n glass?: boolean; // 毛玻璃效果\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n className?: string;\n};\n\nconst Card = ({\n children,\n variant,\n bordered = false,\n dash = false,\n side = false,\n imageFull = false,\n compact = false,\n hover = true, // 默认启用 hover 效果\n glass = true, // 默认启用毛玻璃效果\n size = 'sm',\n className = '',\n}: CardProps) => {\n const baseClass = 'card';\n\n // 默认样式:如果没有指定 variant,则应用毛玻璃效果和阴影\n // 同时添加边界样式 (border + shadow)\n const defaultStyles = glass && !variant ? 'glass shadow-lg' : '';\n\n // 如果指定了 variant,使用该 variant 的背景色,并添加阴影\n const variantClass = variant ? `bg-${variant} text-${variant}-content shadow-md` : '';\n\n const borderedClass = bordered ? 'card-bordered' : '';\n const dashClass = dash ? 'card-dash' : '';\n const sideClass = side ? 'card-side' : '';\n const imageFullClass = imageFull ? 'image-full' : '';\n const compactClass = compact ? 'card-compact' : '';\n const hoverClass = hover ? 'hover:shadow-xl transition-shadow duration-300' : '';\n const sizeClass = size ? `card-${size}` : '';\n\n // 宽度策略:\n // 1. 如果用户指定了 w-/max-w-/min-w-,则完全使用用户的设置\n // 2. 否则不设置固定宽度,让 Card 自然适应父容器(Grid 中自动填充,Flex 中根据内容)\n const hasWidthClass = className.includes('w-') || className.match(/max-w-|min-w-/);\n const widthClass = ''; // 不设置默认宽度,让布局决定\n\n const cardClasses = cn(\n baseClass,\n widthClass,\n defaultStyles,\n variantClass,\n borderedClass,\n dashClass,\n sideClass,\n imageFullClass,\n compactClass,\n hoverClass,\n sizeClass,\n className\n );\n\n return (\n <div className={cardClasses}>\n {children}\n </div>\n );\n};\n\n// Card.Title 组件\nexport type CardTitleProps = {\n children: React.ReactNode;\n className?: string;\n};\n\nconst CardTitle = ({\n children,\n className = '',\n}: CardTitleProps) => {\n const titleClasses = cn('card-title', className);\n\n return (\n <h2 className={titleClasses}>\n {children}\n </h2>\n );\n};\n\n// Card.Body 组件\nexport type CardBodyProps = {\n children: React.ReactNode;\n className?: string;\n};\n\nconst CardBody = ({\n children,\n className = '',\n}: CardBodyProps) => {\n const bodyClasses = cn('card-body', className);\n\n return (\n <div className={bodyClasses}>\n {children}\n </div>\n );\n};\n\n// Card.Actions 组件\nexport type CardActionsProps = {\n children: React.ReactNode;\n className?: string;\n justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';\n};\n\nconst CardActions = ({\n children,\n className = '',\n justify = 'end',\n}: CardActionsProps) => {\n const actionsClasses = cn(\n 'card-actions',\n justify && `justify-${justify}`,\n className\n );\n\n return (\n <div className={actionsClasses}>\n {children}\n </div>\n );\n};\n\n// Card.Figure 组件\nexport type CardFigureProps = {\n children: React.ReactNode;\n className?: string;\n /**\n * 图片填充方式(作用于内部 <img>):\n * - `cover` 等比裁切填满(默认,“撑满”)\n * - `contain` 等比完整展示,可能留白\n * - `fill` 拉伸填满(不保持比例)\n */\n fit?: 'cover' | 'contain' | 'fill';\n /** 图片撑满整个 figure(w/h 100%)。card-side 时建议开启。 */\n fill?: boolean;\n /** 指定 figure 高度(数字按 px,支持 \"16rem\"/\"50%\" 等);内部图片自动填满该高度 */\n height?: number | string;\n};\n\nconst FIT_CLASS: Record<NonNullable<CardFigureProps['fit']>, string> = {\n cover: '[&>img]:object-cover',\n contain: '[&>img]:object-contain',\n fill: '[&>img]:object-fill',\n};\n\nconst toCssSize = (value: number | string | undefined): string | undefined =>\n typeof value === 'number' ? `${value}px` : value;\n\nconst CardFigure = ({\n children,\n className = '',\n fit = 'cover',\n fill = false,\n height,\n}: CardFigureProps) => {\n const figureClasses = cn(\n 'figure',\n FIT_CLASS[fit],\n // 撑满:figure 与内部 img 都占满可用空间\n fill && 'w-full h-full [&>img]:h-full [&>img]:w-full',\n // 指定高度:figure 定高,内部图片自动填满\n height !== undefined && 'w-full [&>img]:h-full [&>img]:w-full',\n className\n );\n\n return (\n <figure className={figureClasses} style={height !== undefined ? { height: toCssSize(height) } : undefined}>\n {children}\n </figure>\n );\n};\n\n// ─── 内部工具:复用基础 Card 的类名拼装逻辑 ──────────────────\n\ninterface CardClassOptions {\n variant?: CardProps['variant'];\n bordered?: boolean;\n glass?: boolean;\n className?: string;\n}\n\nconst buildCardClasses = ({ variant, bordered = false, glass = true, className = '' }: CardClassOptions) => {\n const defaultStyles = glass && !variant ? 'glass shadow-lg' : '';\n const variantClass = variant ? `bg-${variant} text-${variant}-content shadow-md` : '';\n return cn('card', defaultStyles, variantClass, bordered && 'card-bordered', className);\n};\n\nconst ClockGlyph: React.FC<{ className?: string }> = ({ className }) => (\n <svg xmlns=\"http://www.w3.org/2000/svg\" className={cn('h-4 w-4', className)} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path strokeLinecap=\"round\" d=\"M12 7v5l3 2\" />\n </svg>\n);\n\n// ─── Card.Feature:特性卡(图标 + 标题 + 描述) ───────────────\n\nexport type CardFeatureProps = {\n /** Icon node (usually a lucide / inline svg), rendered inside a tinted container. */\n icon?: React.ReactNode;\n /** Feature title. */\n title: React.ReactNode;\n /** Feature description rendered under the title. */\n description?: React.ReactNode;\n /** `vertical` centers everything with the icon on top; `horizontal` puts the icon at the left. */\n layout?: 'vertical' | 'horizontal';\n /** Icon container shape. Defaults to `circle` (vertical) / `square` (horizontal). */\n iconShape?: 'circle' | 'square';\n /** Extra content rendered under the description. */\n children?: React.ReactNode;\n /** Color variant (daisyUI). */\n variant?: CardProps['variant'];\n /** Show a border. */\n bordered?: boolean;\n /** Disable the default glass background. */\n glass?: boolean;\n /** Extra class names on the card. */\n className?: string;\n /** Click handler. */\n onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nconst CardFeature: React.FC<CardFeatureProps> = ({\n icon,\n title,\n description,\n layout = 'vertical',\n iconShape,\n children,\n variant,\n bordered,\n glass = true,\n className = '',\n onClick,\n}) => {\n const shape = iconShape ?? (layout === 'vertical' ? 'circle' : 'square');\n const iconBox = icon && (\n <span\n className={cn(\n 'flex shrink-0 items-center justify-center bg-primary/10 text-primary',\n layout === 'vertical' ? 'size-16' : 'size-12',\n shape === 'circle' ? 'rounded-full' : 'rounded-xl',\n )}\n >\n {icon}\n </span>\n );\n\n return (\n <div\n className={buildCardClasses({ variant, bordered, glass, className })}\n onClick={onClick}\n >\n {layout === 'horizontal' ? (\n <div className=\"card-body flex-row items-center gap-4\">\n {iconBox}\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"font-semibold text-base-content\">{title}</h3>\n {description && (\n <p className=\"mt-0.5 text-sm text-base-content/70\">{description}</p>\n )}\n {children}\n </div>\n </div>\n ) : (\n <div className=\"card-body items-center gap-3 text-center\">\n {iconBox}\n <h3 className=\"text-lg font-bold text-base-content\">{title}</h3>\n {description && (\n <p className=\"text-sm leading-relaxed text-base-content/70\">{description}</p>\n )}\n {children}\n </div>\n )}\n </div>\n );\n};\n\n// ─── Card.Quote:引用卡(引用文本 + 作者 / 日期 + 装饰引号) ──\n\nexport type CardQuoteProps = {\n /** The quoted text. */\n children: React.ReactNode;\n /** Author name (rendered in primary color). */\n author?: React.ReactNode;\n /** Avatar node (e.g. an `<img>` or initial circle) shown before the author. */\n avatar?: React.ReactNode;\n /** Date string/node rendered at the right side of the footer. */\n date?: React.ReactNode;\n /** Hide the decorative quote glyph in the top-right corner. */\n quoteMark?: boolean;\n /** Hide the divider between quote and footer. */\n divider?: boolean;\n /** Color variant (daisyUI). */\n variant?: CardProps['variant'];\n /** Show a border. */\n bordered?: boolean;\n /** Disable the default glass background. */\n glass?: boolean;\n /** Extra class names on the card. */\n className?: string;\n /** Click handler. */\n onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nconst CardQuote: React.FC<CardQuoteProps> = ({\n children,\n author,\n avatar,\n date,\n quoteMark = true,\n divider = true,\n variant,\n bordered,\n glass = true,\n className = '',\n onClick,\n}) => (\n <div\n className={cn(\n buildCardClasses({ variant, bordered, glass, className }),\n 'relative overflow-hidden',\n )}\n onClick={onClick}\n >\n {quoteMark && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute right-5 top-2 select-none font-serif text-6xl leading-none text-primary/25\"\n >\n ”\n </span>\n )}\n <div className=\"card-body gap-0 p-6\">\n <div className=\"relative z-10 text-lg text-base-content\">{children}</div>\n <div\n className={cn(\n 'mt-4 flex items-center justify-between gap-3',\n divider && 'border-t border-base-300 pt-3.5',\n )}\n >\n <div className=\"flex min-w-0 items-center gap-2.5\">\n {avatar && <span className=\"size-8 shrink-0 overflow-hidden rounded-full\">{avatar}</span>}\n {author && (\n <span className=\"truncate text-sm font-medium text-primary\">{author}</span>\n )}\n </div>\n {date && (\n <span className=\"flex shrink-0 items-center gap-1.5 text-sm text-base-content/55\">\n <ClockGlyph />\n {date}\n </span>\n )}\n </div>\n </div>\n </div>\n);\n\n// ─── Card.Meta:元信息卡(标题 + 徽章 + 描述 + 左右页脚) ────\n\nexport type CardMetaProps = {\n /** Card title (top-left). */\n title: React.ReactNode;\n /** Badge / tag node (top-right, e.g. a visibility badge). */\n badge?: React.ReactNode;\n /** Body content under the title row. */\n children?: React.ReactNode;\n /** Footer left slot (e.g. `@author`). */\n footerLeft?: React.ReactNode;\n /** Footer right slot (e.g. a date). */\n footerRight?: React.ReactNode;\n /** Color variant (daisyUI). */\n variant?: CardProps['variant'];\n /** Show a border. */\n bordered?: boolean;\n /** Disable the default glass background. */\n glass?: boolean;\n /** Extra class names on the card. */\n className?: string;\n /** Click handler. */\n onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nconst CardMeta: React.FC<CardMetaProps> = ({\n title,\n badge,\n children,\n footerLeft,\n footerRight,\n variant,\n bordered,\n glass = true,\n className = '',\n onClick,\n}) => (\n <div\n className={buildCardClasses({ variant, bordered, glass, className })}\n onClick={onClick}\n >\n <div className=\"card-body gap-2.5 p-6\">\n <div className=\"flex items-center justify-between gap-3\">\n <h3 className=\"min-w-0 flex-1 truncate text-lg font-bold text-base-content\">{title}</h3>\n {badge && <span className=\"shrink-0\">{badge}</span>}\n </div>\n {children && <div className=\"text-sm text-base-content/75\">{children}</div>}\n {(footerLeft || footerRight) && (\n <div className=\"mt-1.5 flex items-center justify-between gap-3 text-sm text-base-content/55\">\n <span className=\"min-w-0 truncate\">{footerLeft}</span>\n <span className=\"shrink-0\">{footerRight}</span>\n </div>\n )}\n </div>\n </div>\n);\n\n// ─── Card.Info:指标卡(标签 + 数值 + 可选图标 / hint / 加载态) ──\n\nexport type CardInfoTone = 'primary' | 'success' | 'warning' | 'info' | 'error' | 'neutral';\n\nexport type CardInfoProps = {\n /** Metric name (top-left, small muted text). */\n label: React.ReactNode;\n /** Metric value (number or short text). */\n value: React.ReactNode;\n /** Icon node, auto-sized and tinted by tone. */\n icon?: React.ReactNode;\n /** `top` (default) puts the icon at the right of the label row; `left` puts it on the left (horizontal layout). */\n iconPosition?: 'top' | 'left';\n /** 图标底框形状:`square`(默认,方形圆角)/ `circle`(正圆)。 */\n iconShape?: 'square' | 'circle';\n /** 是否给图标加色调底框(默认 `true`);`false` 时渲染不带底框、仅保留 tone 文字色的裸图标。 */\n iconBoxed?: boolean;\n /** Tone (icon text & background color). */\n tone?: CardInfoTone;\n /** Small hint text below the value (e.g. deltas, update time). */\n hint?: React.ReactNode;\n /** Loading state: value renders as a skeleton. */\n loading?: boolean;\n /** Color variant (daisyUI). */\n variant?: CardProps['variant'];\n /** Show a border (default true). */\n bordered?: boolean;\n /** Enable the glass background (default false — solid `bg-base-200`). */\n glass?: boolean;\n /** Extra class names on the card. */\n className?: string;\n /** Click handler. */\n onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nconst INFO_TONE_CLASS: Record<NonNullable<CardInfoProps['tone']>, { text: string; bg: string }> = {\n primary: { text: 'text-primary', bg: 'bg-primary/10' },\n success: { text: 'text-success', bg: 'bg-success/10' },\n warning: { text: 'text-warning', bg: 'bg-warning/10' },\n info: { text: 'text-info', bg: 'bg-info/10' },\n error: { text: 'text-error', bg: 'bg-error/10' },\n neutral: { text: 'text-base-content/70', bg: 'bg-base-content/5' },\n};\n\nconst CardInfo: React.FC<CardInfoProps> = ({\n label,\n value,\n icon,\n iconPosition = 'top',\n iconShape = 'square',\n iconBoxed = true,\n tone = 'primary',\n hint,\n loading = false,\n variant,\n bordered = true,\n glass = false,\n className = '',\n onClick,\n}) => {\n const t = INFO_TONE_CLASS[tone];\n const cardClasses = buildCardClasses({ variant, bordered, glass, className: cn('bg-base-200 p-4', className) });\n\n const iconShapeClass = iconShape === 'circle' ? 'rounded-full' : iconPosition === 'left' ? 'rounded-lg' : 'rounded-md';\n\n // 带色调底框的图标容器\n const boxedIcon = icon && (\n <span\n className={cn(\n iconPosition === 'left'\n ? 'flex shrink-0 items-center justify-center p-2.5 [&>svg]:h-5 [&>svg]:w-5'\n : 'p-1.5 [&>svg]:h-4 [&>svg]:w-4',\n iconShapeClass,\n t.bg,\n t.text,\n )}\n >\n {icon}\n </span>\n );\n\n // 裸图标:无底框,仅 tone 文字色\n const bareIcon = icon && (\n <span\n className={cn(\n iconPosition === 'left' ? 'flex shrink-0' : 'inline-flex',\n '[&>svg]:h-6 [&>svg]:w-6',\n t.text,\n )}\n >\n {icon}\n </span>\n );\n\n // 图标在左、文字居右的水平布局\n if (iconPosition === 'left') {\n return (\n <div\n className={cn(cardClasses, 'transition-shadow duration-300 hover:shadow-xl')}\n onClick={onClick}\n >\n <div className=\"flex items-center gap-3\">\n {iconBoxed ? boxedIcon : bareIcon}\n <div className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-xs font-medium text-base-content/60\">{label}</span>\n {loading ? (\n <div className=\"mt-1 h-6 w-14 animate-pulse rounded bg-base-content/10\" />\n ) : (\n <p className=\"truncate text-xl font-bold leading-tight text-base-content\">{value}</p>\n )}\n {hint && <p className=\"mt-0.5 truncate text-[11px] text-base-content/50\">{hint}</p>}\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div\n className={cn(cardClasses, 'transition-shadow duration-300 hover:shadow-xl')}\n onClick={onClick}\n >\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <span className=\"truncate text-xs font-medium text-base-content/60\">{label}</span>\n {iconBoxed ? boxedIcon : bareIcon}\n </div>\n {loading ? (\n <div className=\"h-7 w-14 animate-pulse rounded bg-base-content/10\" />\n ) : (\n <p className=\"text-2xl font-bold leading-none text-base-content\">{value}</p>\n )}\n {hint && <p className=\"mt-1.5 text-[11px] text-base-content/50\">{hint}</p>}\n </div>\n );\n};\n\n// ─── Card.Stat:统计面板(Grafana 风格:超大数值 + 迷你图 / 角标 / 填充色) ───\n\nexport type CardStatProps = {\n /** 面板标签 */\n label: React.ReactNode;\n /** 面板数值(数字或短文本) */\n value: React.ReactNode;\n /** 色调(决定数值文字色 / 角标三角 / filled 底色) */\n tone?: CardInfoTone;\n /** 加载态:值显示为骨架 */\n loading?: boolean;\n className?: string;\n /** 数值单位后缀(如 `K`、`%`),以小号字体渲染在数值右侧。 */\n unit?: React.ReactNode;\n /** 面板数值字号,默认 `xl`。 */\n valueSize?: 'md' | 'lg' | 'xl' | '2xl';\n /** 面板内容对齐,默认 `left`。 */\n align?: 'left' | 'center';\n /** 直接传入迷你图节点(优先于 sparklineData)。 */\n sparkline?: React.ReactNode;\n /** 迷你图数据:传入数值数组后自动渲染 sparkline。 */\n sparklineData?: number[];\n /** 迷你图类型:折线 / 面积 / 柱状。 */\n sparklineType?: 'line' | 'area' | 'bar';\n /** 迷你图高度(px)。 */\n sparklineHeight?: number;\n /** 右上角状态三角(Grafana 阈值指示)。 */\n indicator?: boolean;\n /** 整卡填充 tone 底色、文字用对应 content 色(图 RabbitMQ 风格)。 */\n filled?: boolean;\n /** 点击回调(面板常用于跳转详情)。 */\n onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;\n};\n\nconst STAT_TONE_TEXT_CLASS: Record<NonNullable<CardStatProps['tone']>, string> = {\n primary: 'text-primary',\n success: 'text-success',\n warning: 'text-warning',\n info: 'text-info',\n error: 'text-error',\n neutral: 'text-base-content/70',\n};\n\n/** filled 模式:整卡 tone 底色 + 对应 content 文字色 */\nconst STAT_FILLED_CLASS: Record<NonNullable<CardStatProps['tone']>, string> = {\n primary: 'bg-primary text-primary-content',\n success: 'bg-success text-success-content',\n warning: 'bg-warning text-warning-content',\n info: 'bg-info text-info-content',\n error: 'bg-error text-error-content',\n neutral: 'bg-neutral text-neutral-content',\n};\n\nconst STAT_VALUE_SIZE_CLASS: Record<NonNullable<CardStatProps['valueSize']> & string, string> = {\n md: 'text-2xl',\n lg: 'text-4xl',\n xl: 'text-5xl',\n '2xl': 'text-6xl',\n};\n\nconst statCssVarColor = (tone: NonNullable<CardStatProps['tone']>) =>\n tone === 'neutral' ? 'var(--color-base-content)' : `var(--color-${tone})`;\n\n/** 由数值数组生成迷你图(viewBox 拉伸铺满,描边不随宽度变形) */\nconst MiniSparkline: React.FC<{\n data: number[];\n type: 'line' | 'area' | 'bar';\n height: number;\n}> = ({ data, type, height }) => {\n if (!data || data.length < 2) return null;\n const w = 100;\n const pad = 3;\n const min = Math.min(...data);\n const max = Math.max(...data);\n const range = max - min || 1;\n const px = (i: number) => (i / (data.length - 1)) * w;\n const py = (v: number) => height - pad - ((v - min) / range) * (height - pad * 2);\n const points = data.map((v, i) => `${px(i).toFixed(2)},${py(v).toFixed(2)}`);\n\n if (type === 'bar') {\n const bw = w / data.length;\n return (\n <svg\n viewBox={`0 0 ${w} ${height}`}\n preserveAspectRatio=\"none\"\n className=\"block w-full\"\n style={{ height }}\n aria-hidden=\"true\"\n >\n {data.map((v, i) => (\n <rect\n key={i}\n x={px(i) - bw / 2}\n y={py(v)}\n width={bw * 0.8}\n height={Math.max(1, height - pad - py(v))}\n fill=\"currentColor\"\n />\n ))}\n </svg>\n );\n }\n\n if (type === 'area') {\n return (\n <svg\n viewBox={`0 0 ${w} ${height}`}\n preserveAspectRatio=\"none\"\n className=\"block w-full\"\n style={{ height }}\n aria-hidden=\"true\"\n >\n <path\n d={`M0,${height} L${points.join(' L')} L${w},${height} Z`}\n fill=\"currentColor\"\n opacity={0.18}\n />\n <polyline\n points={points.join(' ')}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.5}\n vectorEffect=\"non-scaling-stroke\"\n />\n </svg>\n );\n }\n\n return (\n <svg\n viewBox={`0 0 ${w} ${height}`}\n preserveAspectRatio=\"none\"\n className=\"block w-full\"\n style={{ height }}\n aria-hidden=\"true\"\n >\n <polyline\n points={points.join(' ')}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.5}\n vectorEffect=\"non-scaling-stroke\"\n />\n </svg>\n );\n};\n\n/**\n * 统计面板:Grafana stat panel 风格的「标签 + 超大数值 + 迷你图」小卡,\n * 支持单位后缀、折线 / 面积 / 柱状迷你图、角标状态三角与整卡填充色。\n *\n * 简单的「标签 + 数值 + 图标」指标卡请使用 `Card.Info`。\n */\nconst CardStat: React.FC<CardStatProps> = ({\n label,\n value,\n tone = 'primary',\n loading = false,\n className = '',\n unit,\n valueSize = 'xl',\n align = 'left',\n sparkline,\n sparklineData,\n sparklineType = 'line',\n sparklineHeight = 36,\n indicator = false,\n filled = false,\n onClick,\n}) => {\n const sparklineNode =\n sparkline ??\n (sparklineData && sparklineData.length >= 2 ? (\n <MiniSparkline data={sparklineData} type={sparklineType} height={sparklineHeight} />\n ) : null);\n\n const textColor = filled ? '' : STAT_TONE_TEXT_CLASS[tone];\n const mutedText = filled ? 'opacity-80' : 'text-base-content/60';\n\n return (\n <div\n onClick={onClick}\n className={`relative flex flex-col overflow-hidden rounded-lg p-3 ${\n filled ? STAT_FILLED_CLASS[tone] : 'border border-base-300/60 bg-base-200'\n } ${onClick ? 'cursor-pointer transition-colors hover:border-primary/40' : ''} ${className}`.trim()}\n >\n {indicator && !filled && (\n <span\n aria-hidden=\"true\"\n className=\"absolute right-0 top-0 h-0 w-0\"\n style={{\n borderTop: '14px solid ' + statCssVarColor(tone),\n borderLeft: '14px solid transparent',\n }}\n />\n )}\n <div\n className={`truncate text-xs font-medium ${mutedText} ${align === 'center' ? 'text-center' : ''}`.trim()}\n >\n {label}\n </div>\n <div\n className={`flex min-h-[3rem] flex-1 items-center py-1 ${align === 'center' ? 'justify-center' : 'justify-start'}`}\n >\n {loading ? (\n <span\n className={`inline-block h-8 w-16 animate-pulse rounded ${\n filled ? 'bg-white/25' : 'bg-base-content/10'\n }`}\n />\n ) : (\n <span className={`leading-none tracking-tight ${STAT_VALUE_SIZE_CLASS[valueSize]} font-semibold ${filled ? '' : textColor}`.trim()}>\n {value}\n {unit !== undefined && unit !== null && unit !== '' && (\n <span className=\"ml-0.5 text-[0.45em] font-semibold\">{unit}</span>\n )}\n </span>\n )}\n </div>\n {sparklineNode && (\n <div className={`-mx-3 -mb-3 mt-1 ${filled ? 'text-white' : STAT_TONE_TEXT_CLASS[tone]}`}>\n {sparklineNode}\n </div>\n )}\n </div>\n );\n};\n\n// 扩展Card组件,添加子组件\ninterface CardCompound extends React.FC<CardProps> {\n Metric: React.FC<CardMetricProps>;\n Title: React.FC<CardTitleProps>;\n Body: React.FC<CardBodyProps>;\n Actions: React.FC<CardActionsProps>;\n Figure: React.FC<CardFigureProps>;\n /** 特性卡:图标 + 标题 + 描述(垂直居中 / 横向两种布局) */\n Feature: React.FC<CardFeatureProps>;\n /** 引用卡:引用文本 + 作者头像 / 日期 + 装饰引号 */\n Quote: React.FC<CardQuoteProps>;\n /** 元信息卡:标题 + 徽章 + 描述 + 左右页脚 */\n Meta: React.FC<CardMetaProps>;\n /** 指标卡:标签 + 数值 + 可选图标 / hint / 加载态(仪表盘统计卡片) */\n Info: React.FC<CardInfoProps>;\n /** 统计面板:Grafana 风格超大数值 + 迷你折线 / 面积 / 柱状图 / 角标 / 填充色 */\n Stat: React.FC<CardStatProps>;\n}\n\nconst CardWithCompounds = Card as CardCompound;\nCardWithCompounds.Title = CardTitle;\nCardWithCompounds.Body = CardBody;\nCardWithCompounds.Actions = CardActions;\nCardWithCompounds.Figure = CardFigure;\nCardWithCompounds.Feature = CardFeature;\nCardWithCompounds.Quote = CardQuote;\nCardWithCompounds.Meta = CardMeta;\nCardWithCompounds.Info = CardInfo;\nCardWithCompounds.Stat = CardStat;\nCardWithCompounds.Metric = CardMetric;\n\nexport default CardWithCompounds; "],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type GroupButtonOption = {
|
|
3
|
+
value: string;
|
|
4
|
+
label: React.ReactNode;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
disabledReason?: string;
|
|
7
|
+
};
|
|
8
|
+
export type GroupButtonProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'children' | 'defaultValue'> & {
|
|
9
|
+
options: GroupButtonOption[];
|
|
10
|
+
value: string;
|
|
11
|
+
onValueChange: (value: string) => void;
|
|
12
|
+
variant?: 'segmented' | 'pills';
|
|
13
|
+
'aria-label': string;
|
|
14
|
+
};
|
|
15
|
+
declare const GroupButton: ({ options, value, onValueChange, variant, className, ...rest }: GroupButtonProps) => React.JSX.Element;
|
|
16
|
+
export default GroupButton;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import t from "../Button/index.js";
|
|
3
|
+
/* empty css */
|
|
4
|
+
import { useId as n } from "react";
|
|
5
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
6
|
+
//#region src/components/GroupButton/index.tsx
|
|
7
|
+
var a = ({ options: a, value: o, onValueChange: s, variant: c = "segmented", className: l, ...u }) => {
|
|
8
|
+
let d = n();
|
|
9
|
+
return /* @__PURE__ */ r("div", {
|
|
10
|
+
...u,
|
|
11
|
+
role: "group",
|
|
12
|
+
className: e("blade-group-button", `blade-group-button--${c}`, l),
|
|
13
|
+
children: a.map((e, n) => {
|
|
14
|
+
let a = `${d}-reason-${n}`, c = e.disabled ? e.disabledReason : void 0;
|
|
15
|
+
return /* @__PURE__ */ i("div", {
|
|
16
|
+
className: "blade-group-button__option",
|
|
17
|
+
children: [/* @__PURE__ */ r(t, {
|
|
18
|
+
type: "button",
|
|
19
|
+
variant: "ghost",
|
|
20
|
+
className: "blade-group-button__button",
|
|
21
|
+
"aria-pressed": o === e.value,
|
|
22
|
+
disabled: e.disabled,
|
|
23
|
+
"aria-describedby": c ? a : void 0,
|
|
24
|
+
title: c,
|
|
25
|
+
onClick: () => {
|
|
26
|
+
!e.disabled && e.value !== o && s(e.value);
|
|
27
|
+
},
|
|
28
|
+
children: e.label
|
|
29
|
+
}), c && /* @__PURE__ */ r("span", {
|
|
30
|
+
id: a,
|
|
31
|
+
className: "blade-group-button__reason",
|
|
32
|
+
children: c
|
|
33
|
+
})]
|
|
34
|
+
}, e.value);
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { a as default };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/GroupButton/index.tsx"],"sourcesContent":["import React, { useId } from 'react';\nimport Button from '../Button';\nimport { cn } from '../../utils/cn';\nimport './GroupButton.css';\n\nexport type GroupButtonOption = {\n value: string;\n label: React.ReactNode;\n disabled?: boolean;\n disabledReason?: string;\n};\n\nexport type GroupButtonProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'children' | 'defaultValue'> & {\n options: GroupButtonOption[];\n value: string;\n onValueChange: (value: string) => void;\n variant?: 'segmented' | 'pills';\n 'aria-label': string;\n};\n\nconst GroupButton = ({\n options, value, onValueChange, variant = 'segmented', className, ...rest\n}: GroupButtonProps) => {\n const id = useId();\n return (\n <div {...rest} role=\"group\" className={cn('blade-group-button', `blade-group-button--${variant}`, className)}>\n {options.map((option, index) => {\n const reasonId = `${id}-reason-${index}`;\n const reason = option.disabled ? option.disabledReason : undefined;\n return (\n <div className=\"blade-group-button__option\" key={option.value}>\n <Button\n type=\"button\"\n variant=\"ghost\"\n className=\"blade-group-button__button\"\n aria-pressed={value === option.value}\n disabled={option.disabled}\n aria-describedby={reason ? reasonId : undefined}\n title={reason}\n onClick={() => {\n if (!option.disabled && option.value !== value) onValueChange(option.value);\n }}\n >\n {option.label}\n </Button>\n {reason && <span id={reasonId} className=\"blade-group-button__reason\">{reason}</span>}\n </div>\n );\n })}\n </div>\n );\n};\n\nexport default GroupButton;\n"],"mappings":";;;;;;AAoBA,IAAM,KAAe,EACnB,YAAS,UAAO,kBAAe,aAAU,aAAa,cAAW,GAAG,QAC9C;CACtB,IAAM,IAAK,EAAM;CACjB,OACE,kBAAC,OAAD;EAAK,GAAI;EAAM,MAAK;EAAQ,WAAW,EAAG,sBAAsB,uBAAuB,KAAW,CAAS;EACxG,UAAA,EAAQ,KAAK,GAAQ,MAAU;GAC9B,IAAM,IAAW,GAAG,EAAG,UAAU,KAC3B,IAAS,EAAO,WAAW,EAAO,iBAAiB,KAAA;GACzD,OACE,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,WAAU;KACV,gBAAc,MAAU,EAAO;KAC/B,UAAU,EAAO;KACjB,oBAAkB,IAAS,IAAW,KAAA;KACtC,OAAO;KACP,eAAe;MACb,AAAI,CAAC,EAAO,YAAY,EAAO,UAAU,KAAO,EAAc,EAAO,KAAK;KAC5E;KAEC,UAAA,EAAO;IACF,CAAA,GACP,KAAU,kBAAC,QAAD;KAAM,IAAI;KAAU,WAAU;KAA8B,UAAA;IAAa,CAAA,CACjF;GAhB4C,GAAA,EAAO,KAgBnD;EAET,CAAC;CACE,CAAA;AAET"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type GroupedTableColumn<T> = {
|
|
3
|
+
key: string;
|
|
4
|
+
header: React.ReactNode;
|
|
5
|
+
render: (row: T) => React.ReactNode;
|
|
6
|
+
numeric?: boolean;
|
|
7
|
+
rowHeader?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type GroupedTableGroup<T> = {
|
|
10
|
+
key: string;
|
|
11
|
+
label: React.ReactNode;
|
|
12
|
+
rows: readonly T[];
|
|
13
|
+
/** Summary cells keyed by column key; the first column is the group label. */
|
|
14
|
+
summary?: Record<string, React.ReactNode>;
|
|
15
|
+
};
|
|
16
|
+
export type GroupedTableProps<T> = {
|
|
17
|
+
columns: readonly GroupedTableColumn<T>[];
|
|
18
|
+
groups: readonly GroupedTableGroup<T>[];
|
|
19
|
+
rowKey: (row: T) => React.Key;
|
|
20
|
+
caption: React.ReactNode;
|
|
21
|
+
'aria-label': string;
|
|
22
|
+
collapsedKeys?: readonly string[];
|
|
23
|
+
onToggleGroup?: (key: string) => void;
|
|
24
|
+
footer?: {
|
|
25
|
+
label: React.ReactNode;
|
|
26
|
+
summary: Record<string, React.ReactNode>;
|
|
27
|
+
};
|
|
28
|
+
density?: 'compact' | 'comfortable';
|
|
29
|
+
stickyHeader?: boolean;
|
|
30
|
+
maxHeight?: React.CSSProperties['maxHeight'];
|
|
31
|
+
minWidth?: React.CSSProperties['minWidth'];
|
|
32
|
+
loading?: boolean;
|
|
33
|
+
loadingLabel?: string;
|
|
34
|
+
loadingRows?: number;
|
|
35
|
+
className?: string;
|
|
36
|
+
};
|
|
37
|
+
/** Scrollable, accessible grouped records. Formatting and aggregation stay with the caller. */
|
|
38
|
+
export default function GroupedTable<T>({ columns, groups, rowKey, caption, 'aria-label': label, collapsedKeys, onToggleGroup, footer, density, stickyHeader, maxHeight, minWidth, loading, loadingLabel, loadingRows, className, }: GroupedTableProps<T>): React.JSX.Element;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import t from "../Button/index.js";
|
|
3
|
+
import n from "../Table/index.js";
|
|
4
|
+
import r from "../Skeleton/index.js";
|
|
5
|
+
/* empty css */
|
|
6
|
+
import i, { useId as a } from "react";
|
|
7
|
+
import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
8
|
+
//#region src/components/GroupedTable/index.tsx
|
|
9
|
+
function l({ columns: l, groups: u, rowKey: d, caption: f, "aria-label": p, collapsedKeys: m = [], onToggleGroup: h, footer: g, density: _ = "compact", stickyHeader: v = !0, maxHeight: y = 560, minWidth: b = 800, loading: x = !1, loadingLabel: S = "Loading records…", loadingRows: C = 6, className: w }) {
|
|
10
|
+
let T = a(), E = (e) => l.slice(1).map((t) => /* @__PURE__ */ s("td", {
|
|
11
|
+
"data-numeric": t.numeric || void 0,
|
|
12
|
+
children: e?.[t.key]
|
|
13
|
+
}, t.key));
|
|
14
|
+
return /* @__PURE__ */ c("div", {
|
|
15
|
+
className: e("blade-grouped-table", w),
|
|
16
|
+
style: { "--grouped-table-min-width": typeof b == "number" ? `${b}px` : b },
|
|
17
|
+
children: [x && /* @__PURE__ */ s("p", {
|
|
18
|
+
role: "status",
|
|
19
|
+
className: "blade-grouped-table__loading",
|
|
20
|
+
children: S
|
|
21
|
+
}), /* @__PURE__ */ s("div", {
|
|
22
|
+
className: "blade-grouped-table__scroll",
|
|
23
|
+
role: "region",
|
|
24
|
+
"aria-label": p,
|
|
25
|
+
"aria-busy": x,
|
|
26
|
+
tabIndex: 0,
|
|
27
|
+
style: { maxHeight: y },
|
|
28
|
+
children: /* @__PURE__ */ c(n, {
|
|
29
|
+
className: e("blade-grouped-table__table", `blade-grouped-table--${_}`, v && "blade-grouped-table--sticky"),
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ s("caption", { children: f }),
|
|
32
|
+
/* @__PURE__ */ s("colgroup", { children: l.map((e) => /* @__PURE__ */ s("col", {}, e.key)) }),
|
|
33
|
+
/* @__PURE__ */ s("thead", { children: /* @__PURE__ */ s("tr", { children: l.map((e) => /* @__PURE__ */ s("th", {
|
|
34
|
+
scope: "col",
|
|
35
|
+
"data-numeric": e.numeric || void 0,
|
|
36
|
+
children: e.header
|
|
37
|
+
}, e.key)) }) }),
|
|
38
|
+
x ? /* @__PURE__ */ s("tbody", { children: Array.from({ length: C }, (e, t) => /* @__PURE__ */ s("tr", { children: l.map((e) => /* @__PURE__ */ s("td", { children: /* @__PURE__ */ s(r, {}) }, e.key)) }, t)) }) : u.filter((e) => e.rows.length > 0).map((e, n) => {
|
|
39
|
+
let r = !m.includes(e.key), a = `${T}-group-${n}`;
|
|
40
|
+
return /* @__PURE__ */ c(i.Fragment, { children: [/* @__PURE__ */ s("tbody", { children: /* @__PURE__ */ c("tr", {
|
|
41
|
+
className: "blade-grouped-table__summary",
|
|
42
|
+
children: [/* @__PURE__ */ s("th", {
|
|
43
|
+
scope: "row",
|
|
44
|
+
children: h ? /* @__PURE__ */ c(t, {
|
|
45
|
+
type: "button",
|
|
46
|
+
variant: "ghost",
|
|
47
|
+
size: "xs",
|
|
48
|
+
"aria-expanded": r,
|
|
49
|
+
"aria-controls": a,
|
|
50
|
+
onClick: () => h(e.key),
|
|
51
|
+
children: [
|
|
52
|
+
r ? "−" : "+",
|
|
53
|
+
" ",
|
|
54
|
+
e.label,
|
|
55
|
+
" (",
|
|
56
|
+
e.rows.length,
|
|
57
|
+
")"
|
|
58
|
+
]
|
|
59
|
+
}) : /* @__PURE__ */ c(o, { children: [
|
|
60
|
+
e.label,
|
|
61
|
+
" (",
|
|
62
|
+
e.rows.length,
|
|
63
|
+
")"
|
|
64
|
+
] })
|
|
65
|
+
}), E(e.summary)]
|
|
66
|
+
}) }), /* @__PURE__ */ s("tbody", {
|
|
67
|
+
id: a,
|
|
68
|
+
hidden: !r,
|
|
69
|
+
"aria-label": typeof e.label == "string" ? e.label : void 0,
|
|
70
|
+
children: e.rows.map((e) => /* @__PURE__ */ s("tr", { children: l.map((t) => {
|
|
71
|
+
let n = t.rowHeader ? "th" : "td";
|
|
72
|
+
return /* @__PURE__ */ s(n, {
|
|
73
|
+
scope: t.rowHeader ? "row" : void 0,
|
|
74
|
+
"data-numeric": t.numeric || void 0,
|
|
75
|
+
children: t.render(e)
|
|
76
|
+
}, t.key);
|
|
77
|
+
}) }, d(e)))
|
|
78
|
+
})] }, e.key);
|
|
79
|
+
}),
|
|
80
|
+
!x && g && /* @__PURE__ */ s("tfoot", { children: /* @__PURE__ */ c("tr", { children: [/* @__PURE__ */ s("th", {
|
|
81
|
+
scope: "row",
|
|
82
|
+
children: g.label
|
|
83
|
+
}), E(g.summary)] }) })
|
|
84
|
+
]
|
|
85
|
+
})
|
|
86
|
+
})]
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { l as default };
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/GroupedTable/index.tsx"],"sourcesContent":["import React, { useId } from 'react';\nimport Table from '../Table';\nimport Button from '../Button';\nimport Skeleton from '../Skeleton';\nimport { cn } from '../../utils/cn';\nimport './GroupedTable.css';\n\nexport type GroupedTableColumn<T> = {\n key: string;\n header: React.ReactNode;\n render: (row: T) => React.ReactNode;\n numeric?: boolean;\n rowHeader?: boolean;\n};\n\nexport type GroupedTableGroup<T> = {\n key: string;\n label: React.ReactNode;\n rows: readonly T[];\n /** Summary cells keyed by column key; the first column is the group label. */\n summary?: Record<string, React.ReactNode>;\n};\n\nexport type GroupedTableProps<T> = {\n columns: readonly GroupedTableColumn<T>[];\n groups: readonly GroupedTableGroup<T>[];\n rowKey: (row: T) => React.Key;\n caption: React.ReactNode;\n 'aria-label': string;\n collapsedKeys?: readonly string[];\n onToggleGroup?: (key: string) => void;\n footer?: { label: React.ReactNode; summary: Record<string, React.ReactNode> };\n density?: 'compact' | 'comfortable';\n stickyHeader?: boolean;\n maxHeight?: React.CSSProperties['maxHeight'];\n minWidth?: React.CSSProperties['minWidth'];\n loading?: boolean;\n loadingLabel?: string;\n loadingRows?: number;\n className?: string;\n};\n\n/** Scrollable, accessible grouped records. Formatting and aggregation stay with the caller. */\nexport default function GroupedTable<T>({\n columns, groups, rowKey, caption, 'aria-label': label,\n collapsedKeys = [], onToggleGroup, footer, density = 'compact', stickyHeader = true,\n maxHeight = 560, minWidth = 800, loading = false, loadingLabel = 'Loading records…',\n loadingRows = 6, className,\n}: GroupedTableProps<T>) {\n const id = useId();\n const summaryCells = (summary?: Record<string, React.ReactNode>) => columns.slice(1).map(column => (\n <td key={column.key} data-numeric={column.numeric || undefined}>{summary?.[column.key]}</td>\n ));\n return (\n <div className={cn('blade-grouped-table', className)} style={{ '--grouped-table-min-width': typeof minWidth === 'number' ? `${minWidth}px` : minWidth } as React.CSSProperties}>\n {loading && <p role=\"status\" className=\"blade-grouped-table__loading\">{loadingLabel}</p>}\n <div className=\"blade-grouped-table__scroll\" role=\"region\" aria-label={label} aria-busy={loading} tabIndex={0} style={{ maxHeight }}>\n <Table className={cn('blade-grouped-table__table', `blade-grouped-table--${density}`, stickyHeader && 'blade-grouped-table--sticky')}>\n <caption>{caption}</caption>\n <colgroup>{columns.map(column => <col key={column.key} />)}</colgroup>\n <thead><tr>{columns.map(column => <th key={column.key} scope=\"col\" data-numeric={column.numeric || undefined}>{column.header}</th>)}</tr></thead>\n {loading ? <tbody>{Array.from({ length: loadingRows }, (_, index) => <tr key={index}>{columns.map(column => <td key={column.key}><Skeleton /></td>)}</tr>)}</tbody>\n : groups.filter(group => group.rows.length > 0).map((group, index) => {\n const expanded = !collapsedKeys.includes(group.key);\n const bodyId = `${id}-group-${index}`;\n return <React.Fragment key={group.key}>\n <tbody><tr className=\"blade-grouped-table__summary\">\n <th scope=\"row\">\n {onToggleGroup ? <Button type=\"button\" variant=\"ghost\" size=\"xs\" aria-expanded={expanded} aria-controls={bodyId} onClick={() => onToggleGroup(group.key)}>\n {expanded ? '−' : '+'} {group.label} ({group.rows.length})\n </Button> : <>{group.label} ({group.rows.length})</>}\n </th>\n {summaryCells(group.summary)}\n </tr></tbody>\n <tbody id={bodyId} hidden={!expanded} aria-label={typeof group.label === 'string' ? group.label : undefined}>\n {group.rows.map(row => <tr key={rowKey(row)}>{columns.map(column => {\n const Cell = column.rowHeader ? 'th' : 'td';\n return <Cell key={column.key} scope={column.rowHeader ? 'row' : undefined} data-numeric={column.numeric || undefined}>{column.render(row)}</Cell>;\n })}</tr>)}\n </tbody>\n </React.Fragment>;\n })}\n {!loading && footer && <tfoot><tr><th scope=\"row\">{footer.label}</th>{summaryCells(footer.summary)}</tr></tfoot>}\n </Table>\n </div>\n\n </div>\n );\n}\n"],"mappings":";;;;;;;;AA2CA,SAAwB,EAAgB,EACpC,YAAS,WAAQ,WAAQ,YAAS,cAAc,GAChD,mBAAgB,CAAC,GAAG,kBAAe,WAAQ,aAAU,WAAW,kBAAe,IAC/E,eAAY,KAAK,cAAW,KAAK,aAAU,IAAO,kBAAe,oBACjE,iBAAc,GAAG,gBACI;CACrB,IAAM,IAAK,EAAM,GACX,KAAgB,MAA8C,EAAQ,MAAM,CAAC,CAAC,CAAC,KAAI,MACrF,kBAAC,MAAD;EAAqB,gBAAc,EAAO,WAAW,KAAA;EAAY,UAAA,IAAU,EAAO;CAAS,GAAlF,EAAO,GAA2E,CAC9F;CACD,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,uBAAuB,CAAS;EAAG,OAAO,EAAE,6BAA6B,OAAO,KAAa,WAAW,GAAG,EAAS,MAAM,EAAS;EAAtJ,UAAA,CACK,KAAW,kBAAC,KAAD;GAAG,MAAK;GAAS,WAAU;GAAgC,UAAA;EAAgB,CAAA,GACvF,kBAAC,OAAD;GAAK,WAAU;GAA8B,MAAK;GAAS,cAAY;GAAO,aAAW;GAAS,UAAU;GAAG,OAAO,EAAE,aAAU;GAC9H,UAAA,kBAAC,GAAD;IAAO,WAAW,EAAG,8BAA8B,wBAAwB,KAAW,KAAgB,6BAA6B;IAAnI,UAAA;KACI,kBAAC,WAAD,EAAA,UAAU,EAAiB,CAAA;KAC3B,kBAAC,YAAD,EAAA,UAAW,EAAQ,KAAI,MAAU,kBAAC,OAAD,CAAuB,GAAb,EAAO,GAAM,CAAC,EAAY,CAAA;KACrE,kBAAC,SAAD,EAAA,UAAO,kBAAC,MAAD,EAAA,UAAK,EAAQ,KAAI,MAAU,kBAAC,MAAD;MAAqB,OAAM;MAAM,gBAAc,EAAO,WAAW,KAAA;MAAY,UAAA,EAAO;KAAW,GAAtF,EAAO,GAA+E,CAAC,EAAM,CAAA,EAAQ,CAAA;KAC/I,IAAU,kBAAC,SAAD,EAAA,UAAQ,MAAM,KAAK,EAAE,QAAQ,EAAY,IAAI,GAAG,MAAU,kBAAC,MAAD,EAAA,UAAiB,EAAQ,KAAI,MAAU,kBAAC,MAAD,EAAA,UAAqB,kBAAC,GAAD,CAAW,CAAA,EAAK,GAA5B,EAAO,GAAqB,CAAC,EAAM,GAA1E,CAA0E,CAAC,EAAS,CAAA,IAC5J,EAAO,QAAO,MAAS,EAAM,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,GAAO,MAAU;MAClE,IAAM,IAAW,CAAC,EAAc,SAAS,EAAM,GAAG,GAC5C,IAAS,GAAG,EAAG,SAAS;MAC9B,OAAO,kBAAC,EAAM,UAAP,EAAA,UAAA,CACH,kBAAC,SAAD,EAAA,UAAO,kBAAC,MAAD;OAAI,WAAU;OAAd,UAAA,CACH,kBAAC,MAAD;QAAI,OAAM;QACL,UAAA,IAAgB,kBAAC,GAAD;SAAQ,MAAK;SAAS,SAAQ;SAAQ,MAAK;SAAK,iBAAe;SAAU,iBAAe;SAAQ,eAAe,EAAc,EAAM,GAAG;SAAtI,UAAA;UACZ,IAAW,MAAM;UAAI;UAAE,EAAM;UAAM;UAAG,EAAM,KAAK;UAAO;SACrD;QAAI,CAAA,IAAA,kBAAA,GAAA,EAAA,UAAA;SAAG,EAAM;SAAM;SAAG,EAAM,KAAK;SAAO;QAAG,EAAA,CAAA;OACnD,CAAA,GACH,EAAa,EAAM,OAAO,CAC3B;MAAQ,CAAA,EAAA,CAAA,GACZ,kBAAC,SAAD;OAAO,IAAI;OAAQ,QAAQ,CAAC;OAAU,cAAY,OAAO,EAAM,SAAU,WAAW,EAAM,QAAQ,KAAA;OAC7F,UAAA,EAAM,KAAK,KAAI,MAAO,kBAAC,MAAD,EAAA,UAAuB,EAAQ,KAAI,MAAU;QAChE,IAAM,IAAO,EAAO,YAAY,OAAO;QACvC,OAAO,kBAAC,GAAD;SAAuB,OAAO,EAAO,YAAY,QAAQ,KAAA;SAAW,gBAAc,EAAO,WAAW,KAAA;SAAY,UAAA,EAAO,OAAO,CAAG;QAAQ,GAA9H,EAAO,GAAuH;OACpJ,CAAC,EAAM,GAHyB,EAAO,CAAG,CAGnC,CAAC;MACL,CAAA,CACK,EAAA,GAfY,EAAM,GAelB;KACpB,CAAC;KACJ,CAAC,KAAW,KAAU,kBAAC,SAAD,EAAA,UAAO,kBAAC,MAAD,EAAA,UAAA,CAAI,kBAAC,MAAD;MAAI,OAAM;MAAO,UAAA,EAAO;KAAU,CAAA,GAAE,EAAa,EAAO,OAAO,CAAM,EAAA,CAAA,EAAQ,CAAA;IAC5G;;EACN,CAAA,CAEJ;;AAEb"}
|
|
@@ -52,7 +52,8 @@ export type LayoutFlexProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
52
52
|
};
|
|
53
53
|
export type LayoutGroupProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
54
54
|
direction?: React.CSSProperties['flexDirection'];
|
|
55
|
-
wrap
|
|
55
|
+
/** Boolean shorthand is supported alongside CSS wrap values. */
|
|
56
|
+
wrap?: React.CSSProperties['flexWrap'] | boolean;
|
|
56
57
|
align?: LayoutAlign;
|
|
57
58
|
justify?: LayoutJustify;
|
|
58
59
|
gap?: LayoutGap;
|
|
@@ -89,7 +90,8 @@ declare const GridItem: React.ForwardRefExoticComponent<React.HTMLAttributes<HTM
|
|
|
89
90
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
90
91
|
declare const Group: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
|
|
91
92
|
direction?: React.CSSProperties['flexDirection'];
|
|
92
|
-
wrap
|
|
93
|
+
/** Boolean shorthand is supported alongside CSS wrap values. */
|
|
94
|
+
wrap?: React.CSSProperties['flexWrap'] | boolean;
|
|
93
95
|
align?: LayoutAlign;
|
|
94
96
|
justify?: LayoutJustify;
|
|
95
97
|
gap?: LayoutGap;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Layout/index.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport { cn } from '../../utils/cn';\n\nexport type LayoutGap = number | string;\nexport type GridColumns = number | readonly number[] | string;\nexport type GridPlacement = number | string;\nexport type LayoutMaxWidth = number | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full' | string;\nexport type LayoutAlign = React.CSSProperties['alignItems'] | 'start' | 'end';\nexport type LayoutJustify = React.CSSProperties['justifyContent'] | 'start' | 'end' | 'between' | 'around' | 'evenly';\n\nexport type LayoutContainerProps = React.HTMLAttributes<HTMLDivElement> & {\n /** Maximum content width. Numbers are interpreted as pixels. */\n maxWidth?: LayoutMaxWidth;\n /** Horizontal container padding. Numbers are interpreted as pixels. */\n padding?: number | string;\n /** Centers the container when a max width is applied. */\n centered?: boolean;\n /** Makes the container span the available width. */\n fluid?: boolean;\n /** Uses adaptive horizontal padding that grows with the viewport. */\n responsive?: boolean;\n};\n\nexport type LayoutGridProps = React.HTMLAttributes<HTMLDivElement> & {\n /** Column count (12 by default) or a custom ratio such as [1, 2, 1] or \"1fr 2fr 1fr\". */\n columns?: GridColumns;\n gap?: LayoutGap;\n /** Alias for gap. gap takes precedence when both are provided. */\n space?: LayoutGap;\n rowGap?: LayoutGap;\n columnGap?: LayoutGap;\n align?: LayoutAlign;\n justify?: LayoutJustify;\n autoRows?: React.CSSProperties['gridAutoRows'];\n dense?: boolean;\n};\n\nexport type LayoutGridItemProps = React.HTMLAttributes<HTMLDivElement> & {\n /** Number of grid tracks to span, or a valid CSS grid-column value. */\n span?: GridPlacement;\n /** One-based grid column start, or a valid CSS grid-column-start value. */\n start?: GridPlacement;\n /** Number of empty tracks before the item. Ignored when start is set. */\n offset?: number;\n rowSpan?: GridPlacement;\n rowStart?: GridPlacement;\n};\n\nexport type LayoutFlexProps = React.HTMLAttributes<HTMLDivElement> & {\n direction?: React.CSSProperties['flexDirection'];\n wrap?: React.CSSProperties['flexWrap'];\n align?: LayoutAlign;\n justify?: LayoutJustify;\n gap?: LayoutGap;\n rowGap?: LayoutGap;\n columnGap?: LayoutGap;\n inline?: boolean;\n};\n\nexport type LayoutGroupProps = React.HTMLAttributes<HTMLDivElement> & {\n direction?: React.CSSProperties['flexDirection'];\n wrap?: React.CSSProperties['flexWrap'];\n align?: LayoutAlign;\n justify?: LayoutJustify;\n gap?: LayoutGap;\n fullWidth?: boolean;\n};\n\nexport type LayoutSpaceProps = React.HTMLAttributes<HTMLSpanElement> & {\n direction?: 'horizontal' | 'vertical';\n /** Width for horizontal spaces and height for vertical spaces. */\n size?: number | string;\n /** Makes the spacer consume remaining flex space. */\n flex?: number | string;\n};\n\nconst maxWidths: Record<string, string> = {\n sm: '640px',\n md: '768px',\n lg: '1024px',\n xl: '1280px',\n '2xl': '1536px',\n full: '100%',\n};\n\nconst cssSize = (value: number | string | undefined): string | undefined =>\n typeof value === 'number' ? `${value}px` : value;\n\nconst cssMaxWidth = (value: LayoutMaxWidth | undefined): string | undefined =>\n typeof value === 'string' ? maxWidths[value] ?? value : cssSize(value);\n\nconst cssAlign = (value: LayoutAlign | undefined): React.CSSProperties['alignItems'] =>\n value === 'start' || value === 'end' ? `flex-${value}` : value;\n\nconst cssJustify = (value: LayoutJustify | undefined): React.CSSProperties['justifyContent'] => {\n const aliases: Record<string, React.CSSProperties['justifyContent']> = {\n start: 'flex-start',\n end: 'flex-end',\n between: 'space-between',\n around: 'space-around',\n evenly: 'space-evenly',\n };\n return typeof value === 'string' ? aliases[value] ?? value : value;\n};\n\nconst cssGap = (value: LayoutGap | undefined): string | undefined =>\n typeof value === 'number' ? `${value}px` : value;\n\nconst positiveInteger = (value: number, name: string): number => {\n if (!Number.isInteger(value) || value <= 0) {\n throw new Error(`Layout.${name} must be a positive integer.`);\n }\n return value;\n};\n\nconst positiveNumber = (value: number, name: string): number => {\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error(`Layout.${name} must be a positive number.`);\n }\n return value;\n};\n\nconst gridTemplateColumns = (columns: GridColumns): string => {\n if (typeof columns === 'number') {\n return `repeat(${positiveInteger(columns, 'Grid columns')}, minmax(0, 1fr))`;\n }\n\n if (typeof columns === 'string') {\n return columns;\n }\n\n if (columns.length === 0) {\n throw new Error('Layout.Grid columns ratio must contain at least one value.');\n }\n\n return columns\n .map((ratio) => `${positiveNumber(ratio, 'Grid ratio')}fr`)\n .join(' ');\n};\n\nconst gridSpan = (value: GridPlacement | undefined): string | number | undefined =>\n typeof value === 'number'\n ? `span ${positiveInteger(value, 'Grid.Item span')} / span ${value}`\n : value;\n\nconst gridStart = (value: GridPlacement | undefined, name: string): string | number | undefined =>\n typeof value === 'number' ? positiveInteger(value, name) : value;\n\nconst Container = forwardRef<HTMLDivElement, LayoutContainerProps>(function LayoutContainer(\n {\n maxWidth = 1280,\n padding = '1rem',\n centered = true,\n fluid = false,\n responsive = false,\n className = '',\n style,\n ...props\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn('dishui-layout-container', className)}\n style={{\n width: '100%',\n maxWidth: fluid ? undefined : cssMaxWidth(maxWidth),\n marginInline: centered && !fluid ? 'auto' : undefined,\n paddingInline: responsive\n ? `clamp(${cssSize(padding)}, 4vw, max(${cssSize(padding)}, 2rem))`\n : cssSize(padding),\n ...style,\n }}\n {...props}\n />\n );\n});\n\nconst GridItem = forwardRef<HTMLDivElement, LayoutGridItemProps>(function LayoutGridItem(\n { span, start, offset, rowSpan, rowStart, className = '', style, ...props },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn('dishui-layout-grid-item', className)}\n style={{\n gridColumn: gridSpan(span),\n gridColumnStart: gridStart(\n start ?? (offset !== undefined ? positiveInteger(offset + 1, 'Grid.Item offset') : undefined),\n 'Grid.Item start',\n ),\n gridRow: gridSpan(rowSpan),\n gridRowStart: gridStart(rowStart, 'Grid.Item rowStart'),\n minWidth: 0,\n ...style,\n }}\n {...props}\n />\n );\n});\n\nconst GridRoot = forwardRef<HTMLDivElement, LayoutGridProps>(function LayoutGrid(\n {\n columns = 12,\n gap,\n space,\n rowGap,\n columnGap,\n align,\n justify,\n autoRows,\n dense = false,\n className = '',\n style,\n ...props\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn('dishui-layout-grid', className)}\n style={{\n display: 'grid',\n gridTemplateColumns: gridTemplateColumns(columns),\n gap: cssGap(gap ?? space),\n rowGap: cssGap(rowGap),\n columnGap: cssGap(columnGap),\n alignItems: cssAlign(align),\n justifyContent: cssJustify(justify),\n gridAutoRows: autoRows,\n gridAutoFlow: dense ? 'row dense' : undefined,\n ...style,\n }}\n {...props}\n />\n );\n});\n\nconst Group = forwardRef<HTMLDivElement, LayoutGroupProps>(function LayoutGroup(\n {\n direction = 'row',\n wrap = 'nowrap',\n align = 'center',\n justify = 'start',\n gap,\n fullWidth = false,\n className = '',\n style,\n ...props\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn('dishui-layout-group', className)}\n style={{\n display: 'flex',\n width: fullWidth ? '100%' : undefined,\n flexDirection: direction,\n flexWrap: wrap,\n alignItems: cssAlign(align),\n justifyContent: cssJustify(justify),\n gap: cssGap(gap),\n ...style,\n }}\n {...props}\n />\n );\n});\n\nconst Space = forwardRef<HTMLSpanElement, LayoutSpaceProps>(function LayoutSpace(\n { direction = 'horizontal', size = 16, flex, className = '', style, ...props },\n ref,\n) {\n const dimension = cssSize(size);\n\n return (\n <span\n ref={ref}\n aria-hidden=\"true\"\n className={cn('dishui-layout-space', className)}\n style={{\n display: 'block',\n flex: flex ?? '0 0 auto',\n width: direction === 'horizontal' ? dimension : '100%',\n minWidth: direction === 'horizontal' ? dimension : undefined,\n height: direction === 'vertical' ? dimension : 1,\n minHeight: direction === 'vertical' ? dimension : undefined,\n pointerEvents: 'none',\n ...style,\n }}\n {...props}\n />\n );\n});\n\nconst Flex = forwardRef<HTMLDivElement, LayoutFlexProps>(function LayoutFlex(\n {\n direction = 'row',\n wrap = 'nowrap',\n align,\n justify,\n gap,\n rowGap,\n columnGap,\n inline = false,\n className = '',\n style,\n ...props\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn('dishui-layout-flex', className)}\n style={{\n display: inline ? 'inline-flex' : 'flex',\n flexDirection: direction,\n flexWrap: wrap,\n alignItems: cssAlign(align),\n justifyContent: cssJustify(justify),\n gap: cssGap(gap),\n rowGap: cssGap(rowGap),\n columnGap: cssGap(columnGap),\n ...style,\n }}\n {...props}\n />\n );\n});\n\ninterface LayoutGridCompound extends React.ForwardRefExoticComponent<\n LayoutGridProps & React.RefAttributes<HTMLDivElement>\n> {\n Item: typeof GridItem;\n}\n\nconst Grid = GridRoot as LayoutGridCompound;\nGrid.Item = GridItem;\n\nexport interface LayoutCompound {\n Container: typeof Container;\n Grid: LayoutGridCompound;\n Flex: typeof Flex;\n Group: typeof Group;\n Space: typeof Space;\n}\n\nconst Layout: LayoutCompound = {\n Container,\n Grid,\n Flex,\n Group,\n Space,\n};\n\nexport { Container, Grid, GridItem, Flex, Group, Space };\nexport default Layout;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Layout/index.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport { cn } from '../../utils/cn';\n\nexport type LayoutGap = number | string;\nexport type GridColumns = number | readonly number[] | string;\nexport type GridPlacement = number | string;\nexport type LayoutMaxWidth = number | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full' | string;\nexport type LayoutAlign = React.CSSProperties['alignItems'] | 'start' | 'end';\nexport type LayoutJustify = React.CSSProperties['justifyContent'] | 'start' | 'end' | 'between' | 'around' | 'evenly';\n\nexport type LayoutContainerProps = React.HTMLAttributes<HTMLDivElement> & {\n /** Maximum content width. Numbers are interpreted as pixels. */\n maxWidth?: LayoutMaxWidth;\n /** Horizontal container padding. Numbers are interpreted as pixels. */\n padding?: number | string;\n /** Centers the container when a max width is applied. */\n centered?: boolean;\n /** Makes the container span the available width. */\n fluid?: boolean;\n /** Uses adaptive horizontal padding that grows with the viewport. */\n responsive?: boolean;\n};\n\nexport type LayoutGridProps = React.HTMLAttributes<HTMLDivElement> & {\n /** Column count (12 by default) or a custom ratio such as [1, 2, 1] or \"1fr 2fr 1fr\". */\n columns?: GridColumns;\n gap?: LayoutGap;\n /** Alias for gap. gap takes precedence when both are provided. */\n space?: LayoutGap;\n rowGap?: LayoutGap;\n columnGap?: LayoutGap;\n align?: LayoutAlign;\n justify?: LayoutJustify;\n autoRows?: React.CSSProperties['gridAutoRows'];\n dense?: boolean;\n};\n\nexport type LayoutGridItemProps = React.HTMLAttributes<HTMLDivElement> & {\n /** Number of grid tracks to span, or a valid CSS grid-column value. */\n span?: GridPlacement;\n /** One-based grid column start, or a valid CSS grid-column-start value. */\n start?: GridPlacement;\n /** Number of empty tracks before the item. Ignored when start is set. */\n offset?: number;\n rowSpan?: GridPlacement;\n rowStart?: GridPlacement;\n};\n\nexport type LayoutFlexProps = React.HTMLAttributes<HTMLDivElement> & {\n direction?: React.CSSProperties['flexDirection'];\n wrap?: React.CSSProperties['flexWrap'];\n align?: LayoutAlign;\n justify?: LayoutJustify;\n gap?: LayoutGap;\n rowGap?: LayoutGap;\n columnGap?: LayoutGap;\n inline?: boolean;\n};\n\nexport type LayoutGroupProps = React.HTMLAttributes<HTMLDivElement> & {\n direction?: React.CSSProperties['flexDirection'];\n /** Boolean shorthand is supported alongside CSS wrap values. */\n wrap?: React.CSSProperties['flexWrap'] | boolean;\n align?: LayoutAlign;\n justify?: LayoutJustify;\n gap?: LayoutGap;\n fullWidth?: boolean;\n};\n\nexport type LayoutSpaceProps = React.HTMLAttributes<HTMLSpanElement> & {\n direction?: 'horizontal' | 'vertical';\n /** Width for horizontal spaces and height for vertical spaces. */\n size?: number | string;\n /** Makes the spacer consume remaining flex space. */\n flex?: number | string;\n};\n\nconst maxWidths: Record<string, string> = {\n sm: '640px',\n md: '768px',\n lg: '1024px',\n xl: '1280px',\n '2xl': '1536px',\n full: '100%',\n};\n\nconst cssSize = (value: number | string | undefined): string | undefined =>\n typeof value === 'number' ? `${value}px` : value;\n\nconst cssMaxWidth = (value: LayoutMaxWidth | undefined): string | undefined =>\n typeof value === 'string' ? maxWidths[value] ?? value : cssSize(value);\n\nconst cssAlign = (value: LayoutAlign | undefined): React.CSSProperties['alignItems'] =>\n value === 'start' || value === 'end' ? `flex-${value}` : value;\n\nconst cssJustify = (value: LayoutJustify | undefined): React.CSSProperties['justifyContent'] => {\n const aliases: Record<string, React.CSSProperties['justifyContent']> = {\n start: 'flex-start',\n end: 'flex-end',\n between: 'space-between',\n around: 'space-around',\n evenly: 'space-evenly',\n };\n return typeof value === 'string' ? aliases[value] ?? value : value;\n};\n\nconst cssGap = (value: LayoutGap | undefined): string | undefined =>\n typeof value === 'number' ? `${value}px` : value;\n\nconst positiveInteger = (value: number, name: string): number => {\n if (!Number.isInteger(value) || value <= 0) {\n throw new Error(`Layout.${name} must be a positive integer.`);\n }\n return value;\n};\n\nconst positiveNumber = (value: number, name: string): number => {\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error(`Layout.${name} must be a positive number.`);\n }\n return value;\n};\n\nconst gridTemplateColumns = (columns: GridColumns): string => {\n if (typeof columns === 'number') {\n return `repeat(${positiveInteger(columns, 'Grid columns')}, minmax(0, 1fr))`;\n }\n\n if (typeof columns === 'string') {\n return columns;\n }\n\n if (columns.length === 0) {\n throw new Error('Layout.Grid columns ratio must contain at least one value.');\n }\n\n return columns\n .map((ratio) => `${positiveNumber(ratio, 'Grid ratio')}fr`)\n .join(' ');\n};\n\nconst gridSpan = (value: GridPlacement | undefined): string | number | undefined =>\n typeof value === 'number'\n ? `span ${positiveInteger(value, 'Grid.Item span')} / span ${value}`\n : value;\n\nconst gridStart = (value: GridPlacement | undefined, name: string): string | number | undefined =>\n typeof value === 'number' ? positiveInteger(value, name) : value;\n\nconst Container = forwardRef<HTMLDivElement, LayoutContainerProps>(function LayoutContainer(\n {\n maxWidth = 1280,\n padding = '1rem',\n centered = true,\n fluid = false,\n responsive = false,\n className = '',\n style,\n ...props\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn('dishui-layout-container', className)}\n style={{\n width: '100%',\n maxWidth: fluid ? undefined : cssMaxWidth(maxWidth),\n marginInline: centered && !fluid ? 'auto' : undefined,\n paddingInline: responsive\n ? `clamp(${cssSize(padding)}, 4vw, max(${cssSize(padding)}, 2rem))`\n : cssSize(padding),\n ...style,\n }}\n {...props}\n />\n );\n});\n\nconst GridItem = forwardRef<HTMLDivElement, LayoutGridItemProps>(function LayoutGridItem(\n { span, start, offset, rowSpan, rowStart, className = '', style, ...props },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn('dishui-layout-grid-item', className)}\n style={{\n gridColumn: gridSpan(span),\n gridColumnStart: gridStart(\n start ?? (offset !== undefined ? positiveInteger(offset + 1, 'Grid.Item offset') : undefined),\n 'Grid.Item start',\n ),\n gridRow: gridSpan(rowSpan),\n gridRowStart: gridStart(rowStart, 'Grid.Item rowStart'),\n minWidth: 0,\n ...style,\n }}\n {...props}\n />\n );\n});\n\nconst GridRoot = forwardRef<HTMLDivElement, LayoutGridProps>(function LayoutGrid(\n {\n columns = 12,\n gap,\n space,\n rowGap,\n columnGap,\n align,\n justify,\n autoRows,\n dense = false,\n className = '',\n style,\n ...props\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn('dishui-layout-grid', className)}\n style={{\n display: 'grid',\n gridTemplateColumns: gridTemplateColumns(columns),\n gap: cssGap(gap ?? space),\n rowGap: cssGap(rowGap),\n columnGap: cssGap(columnGap),\n alignItems: cssAlign(align),\n justifyContent: cssJustify(justify),\n gridAutoRows: autoRows,\n gridAutoFlow: dense ? 'row dense' : undefined,\n ...style,\n }}\n {...props}\n />\n );\n});\n\nconst Group = forwardRef<HTMLDivElement, LayoutGroupProps>(function LayoutGroup(\n {\n direction = 'row',\n wrap = 'nowrap',\n align = 'center',\n justify = 'start',\n gap,\n fullWidth = false,\n className = '',\n style,\n ...props\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn('dishui-layout-group', className)}\n style={{\n display: 'flex',\n width: fullWidth ? '100%' : undefined,\n flexDirection: direction,\n flexWrap: typeof wrap === 'boolean' ? (wrap ? 'wrap' : 'nowrap') : wrap,\n alignItems: cssAlign(align),\n justifyContent: cssJustify(justify),\n gap: cssGap(gap),\n ...style,\n }}\n {...props}\n />\n );\n});\n\nconst Space = forwardRef<HTMLSpanElement, LayoutSpaceProps>(function LayoutSpace(\n { direction = 'horizontal', size = 16, flex, className = '', style, ...props },\n ref,\n) {\n const dimension = cssSize(size);\n\n return (\n <span\n ref={ref}\n aria-hidden=\"true\"\n className={cn('dishui-layout-space', className)}\n style={{\n display: 'block',\n flex: flex ?? '0 0 auto',\n width: direction === 'horizontal' ? dimension : '100%',\n minWidth: direction === 'horizontal' ? dimension : undefined,\n height: direction === 'vertical' ? dimension : 1,\n minHeight: direction === 'vertical' ? dimension : undefined,\n pointerEvents: 'none',\n ...style,\n }}\n {...props}\n />\n );\n});\n\nconst Flex = forwardRef<HTMLDivElement, LayoutFlexProps>(function LayoutFlex(\n {\n direction = 'row',\n wrap = 'nowrap',\n align,\n justify,\n gap,\n rowGap,\n columnGap,\n inline = false,\n className = '',\n style,\n ...props\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn('dishui-layout-flex', className)}\n style={{\n display: inline ? 'inline-flex' : 'flex',\n flexDirection: direction,\n flexWrap: wrap,\n alignItems: cssAlign(align),\n justifyContent: cssJustify(justify),\n gap: cssGap(gap),\n rowGap: cssGap(rowGap),\n columnGap: cssGap(columnGap),\n ...style,\n }}\n {...props}\n />\n );\n});\n\ninterface LayoutGridCompound extends React.ForwardRefExoticComponent<\n LayoutGridProps & React.RefAttributes<HTMLDivElement>\n> {\n Item: typeof GridItem;\n}\n\nconst Grid = GridRoot as LayoutGridCompound;\nGrid.Item = GridItem;\n\nexport interface LayoutCompound {\n Container: typeof Container;\n Grid: LayoutGridCompound;\n Flex: typeof Flex;\n Group: typeof Group;\n Space: typeof Space;\n}\n\nconst Layout: LayoutCompound = {\n Container,\n Grid,\n Flex,\n Group,\n Space,\n};\n\nexport { Container, Grid, GridItem, Flex, Group, Space };\nexport default Layout;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type NavigationListItem = {
|
|
3
|
+
value: string;
|
|
4
|
+
label: React.ReactNode;
|
|
5
|
+
count?: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
export type NavigationListProps = {
|
|
8
|
+
'aria-label': string;
|
|
9
|
+
items: readonly NavigationListItem[];
|
|
10
|
+
value: string;
|
|
11
|
+
onValueChange: (value: string) => void;
|
|
12
|
+
/** Optional selectable parent with a separate disclosure control. */
|
|
13
|
+
root?: NavigationListItem;
|
|
14
|
+
defaultExpanded?: boolean;
|
|
15
|
+
};
|
|
16
|
+
/** A single-level navigation/filter list with native keyboard-operable controls. */
|
|
17
|
+
export default function NavigationList({ items, value, onValueChange, root, defaultExpanded, 'aria-label': label }: NavigationListProps): React.JSX.Element;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import e from "../Button/index.js";
|
|
2
|
+
/* empty css */
|
|
3
|
+
import { useId as t, useState as n } from "react";
|
|
4
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/NavigationList/index.tsx
|
|
6
|
+
function a({ items: a, value: o, onValueChange: s, root: c, defaultExpanded: l = !0, "aria-label": u }) {
|
|
7
|
+
let [d, f] = n(l), p = t(), m = (t) => /* @__PURE__ */ i(e, {
|
|
8
|
+
type: "button",
|
|
9
|
+
variant: "ghost",
|
|
10
|
+
className: "blade-navigation-list__item",
|
|
11
|
+
"aria-pressed": o === t.value,
|
|
12
|
+
onClick: () => s(t.value),
|
|
13
|
+
children: [/* @__PURE__ */ r("span", {
|
|
14
|
+
className: "blade-navigation-list__label",
|
|
15
|
+
children: t.label
|
|
16
|
+
}), t.count !== void 0 && /* @__PURE__ */ r("span", {
|
|
17
|
+
className: "blade-navigation-list__count",
|
|
18
|
+
children: t.count
|
|
19
|
+
})]
|
|
20
|
+
});
|
|
21
|
+
return /* @__PURE__ */ i("div", {
|
|
22
|
+
className: "blade-navigation-list",
|
|
23
|
+
role: "group",
|
|
24
|
+
"aria-label": u,
|
|
25
|
+
children: [c && /* @__PURE__ */ i("div", {
|
|
26
|
+
className: "blade-navigation-list__root",
|
|
27
|
+
children: [/* @__PURE__ */ r(e, {
|
|
28
|
+
type: "button",
|
|
29
|
+
variant: "ghost",
|
|
30
|
+
size: "xs",
|
|
31
|
+
"aria-label": `${d ? "折叠" : "展开"}${u}`,
|
|
32
|
+
"aria-expanded": d,
|
|
33
|
+
"aria-controls": p,
|
|
34
|
+
onClick: () => f((e) => !e),
|
|
35
|
+
children: d ? "−" : "+"
|
|
36
|
+
}), m(c)]
|
|
37
|
+
}), /* @__PURE__ */ r("ul", {
|
|
38
|
+
id: p,
|
|
39
|
+
hidden: !!c && !d,
|
|
40
|
+
className: c ? "blade-navigation-list__children" : void 0,
|
|
41
|
+
children: a.map((e) => /* @__PURE__ */ r("li", { children: m(e) }, e.value))
|
|
42
|
+
})]
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { a as default };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/NavigationList/index.tsx"],"sourcesContent":["import React, { useId, useState } from 'react';\nimport Button from '../Button';\nimport './NavigationList.css';\n\nexport type NavigationListItem = {\n value: string;\n label: React.ReactNode;\n count?: React.ReactNode;\n};\n\nexport type NavigationListProps = {\n 'aria-label': string;\n items: readonly NavigationListItem[];\n value: string;\n onValueChange: (value: string) => void;\n /** Optional selectable parent with a separate disclosure control. */\n root?: NavigationListItem;\n defaultExpanded?: boolean;\n};\n\n/** A single-level navigation/filter list with native keyboard-operable controls. */\nexport default function NavigationList({ items, value, onValueChange, root, defaultExpanded = true, 'aria-label': label }: NavigationListProps) {\n const [expanded, setExpanded] = useState(defaultExpanded);\n const id = useId();\n const itemButton = (item: NavigationListItem) => <Button type=\"button\" variant=\"ghost\" className=\"blade-navigation-list__item\" aria-pressed={value === item.value} onClick={() => onValueChange(item.value)}>\n <span className=\"blade-navigation-list__label\">{item.label}</span>\n {item.count !== undefined && <span className=\"blade-navigation-list__count\">{item.count}</span>}\n </Button>;\n return <div className=\"blade-navigation-list\" role=\"group\" aria-label={label}>\n {root && <div className=\"blade-navigation-list__root\">\n <Button type=\"button\" variant=\"ghost\" size=\"xs\" aria-label={`${expanded ? '折叠' : '展开'}${label}`} aria-expanded={expanded} aria-controls={id} onClick={() => setExpanded(current => !current)}>{expanded ? '−' : '+'}</Button>\n {itemButton(root)}\n </div>}\n <ul id={id} hidden={!!root && !expanded} className={root ? 'blade-navigation-list__children' : undefined}>\n {items.map(item => <li key={item.value}>{itemButton(item)}</li>)}\n </ul>\n </div>;\n}\n"],"mappings":";;;;;AAqBA,SAAwB,EAAe,EAAE,UAAO,UAAO,kBAAe,SAAM,qBAAkB,IAAM,cAAc,KAA8B;CAC5I,IAAM,CAAC,GAAU,KAAe,EAAS,CAAe,GAClD,IAAK,EAAM,GACX,KAAc,MAA6B,kBAAC,GAAD;EAAQ,MAAK;EAAS,SAAQ;EAAQ,WAAU;EAA8B,gBAAc,MAAU,EAAK;EAAO,eAAe,EAAc,EAAK,KAAK;EAAzJ,UAAA,CAC7C,kBAAC,QAAD;GAAM,WAAU;GAAgC,UAAA,EAAK;EAAY,CAAA,GAChE,EAAK,UAAU,KAAA,KAAa,kBAAC,QAAD;GAAM,WAAU;GAAgC,UAAA,EAAK;EAAY,CAAA,CAC1F;;CACR,OAAO,kBAAC,OAAD;EAAK,WAAU;EAAwB,MAAK;EAAQ,cAAY;EAAhE,UAAA,CACF,KAAQ,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACL,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAQ,MAAK;IAAK,cAAY,GAAG,IAAW,OAAO,OAAO;IAAS,iBAAe;IAAU,iBAAe;IAAI,eAAe,GAAY,MAAW,CAAC,CAAO;IAAI,UAAA,IAAW,MAAM;GAAY,CAAA,GAC3N,EAAW,CAAI,CACf;EACL,CAAA,GAAA,kBAAC,MAAD;GAAQ;GAAI,QAAQ,CAAC,CAAC,KAAQ,CAAC;GAAU,WAAW,IAAO,oCAAoC,KAAA;GAC1F,UAAA,EAAM,KAAI,MAAQ,kBAAC,MAAD,EAAA,UAAsB,EAAW,CAAI,EAAM,GAAlC,EAAK,KAA6B,CAAC;EAC/D,CAAA,CACH;;AACT"}
|