-
Notifications
You must be signed in to change notification settings - Fork 141
feat(table): align table design in fe #2102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Fiona2016
wants to merge
46
commits into
main
Choose a base branch
from
feat-table-design-srm-0517
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
a08069c
fix(table): set table-body-sort-bg per design spec
Fiona2016 5ff8a6d
fix(table): force header nowrap per design rule §5
Fiona2016 ee0c0cc
fix(table): default pageSize 15 on alert-mutes / alert-subscribes / j…
Fiona2016 1d3dedc
fix(alert-subscribes): combine name + business group into Column1
Fiona2016 f06da5e
fix(alert-mutes): collapse name + datasource type + business group in…
Fiona2016 53afed2
fix(job-tpls): collapse title + ID + business group into Column1
Fiona2016 efa0f94
fix(job-tasks): collapse title + ID + business group into Column1
Fiona2016 3d661d8
fix(users): collapse identity columns into Column1
Fiona2016 f5b951b
fix(alert-rules): collapse name + cate + business group + severity in…
Fiona2016 69be9a3
fix(table): consolidate row actions into overflow menus
Fiona2016 44bce41
fix(table): use overflow menus on route list pages
Fiona2016 412fdac
fix(table): collapse remaining menu route actions
Fiona2016 ad19f99
feat(table): add shared action dropdown
Fiona2016 194da29
fix(table): align ai config actions
Fiona2016 0555ce2
fix(table): align notification actions
Fiona2016 e44a95f
fix(table): align alert event actions
Fiona2016 2fc3e1f
fix(table): align builtin component actions
Fiona2016 77a7986
fix(table): align data route actions
Fiona2016 d39465f
fix(table): align task and user actions
Fiona2016 e5b4a5b
fix(table): forward action dropdown trigger events
Fiona2016 573387d
Merge remote-tracking branch 'origin/main' into feat-table-design-srm…
Fiona2016 84b5a39
Merge remote-tracking branch 'origin/main' into feat-table-design-srm…
Fiona2016 439d4f1
refactor(table): compact primary list metadata columns
Fiona2016 d29310e
feat(table): refine users list primary column
Fiona2016 920cd99
feat(table): refine sorter interaction
Fiona2016 1934313
feat(table): unify route table tags
Fiona2016 0871dc4
style(table): align sorted column body bg with header
Fiona2016 1d4e465
fix(table): keep fixed cells opaque
Fiona2016 fab7e0a
feat(table): fix route table action columns
Fiona2016 617fcfe
fix(table): remove measure-row collapse and tune fixed-column edge
Fiona2016 73b5a4c
fix(table): stabilize task table layouts
Fiona2016 552d2b4
fix(table): refine dropdown interactions and sorter style
Fiona2016 46c0542
fix(table): align sorter icon with title
Fiona2016 8142e2e
fix(table): use fill-2-5 for sorted column bg
Fiona2016 dad392d
fix(table): remove sorted column color override
Fiona2016 f74a5cc
feat(alerts): add event tag collapse toggle
Fiona2016 5de23be
fix(table): apply acceptance feedback on 0517
Fiona2016 0691219
feat(table): normalize table action dropdown alignment and trigger
jsers 60ac063
fix(table): comment out selected row styles to prevent fixed column b…
jsers 1a8f2d9
feat(alertRules): replace event count column icons with Tags component
jsers 79c38e1
refactor(tags): relocate Tags component to shared directory and adopt…
jsers 3bf95de
feat(alertRules): replace TableTags with generic Tags and add severit…
jsers a53ed5c
refactor(alertRules): render null instead of '-' for empty datasource…
jsers d27dde7
refactor(table): unify table styling and Tags component usage
jsers 5338bb0
refactor(table): unify tag rendering and replace TableTags with Tags …
jsers 0d17533
refactor: add explicit type annotations to callback parameters
jsers File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import React from 'react'; | ||
| import { Button } from 'antd'; | ||
| import type { ButtonProps } from 'antd/lib/button'; | ||
| import { | ||
| CheckCircle, | ||
| Copy, | ||
| ExternalLink, | ||
| Eye, | ||
| Link as LinkIcon, | ||
| MoreVertical, | ||
| Network, | ||
| Pencil, | ||
| Play, | ||
| Search, | ||
| Settings, | ||
| ShieldCheck, | ||
| Sparkles, | ||
| Trash2, | ||
| } from 'lucide-react'; | ||
| import classNames from 'classnames'; | ||
| import { Link, LinkProps } from 'react-router-dom'; | ||
|
|
||
| const tableActionIconMap = { | ||
| default: CheckCircle, | ||
| edit: Pencil, | ||
| view: Eye, | ||
| settings: Settings, | ||
| access: Network, | ||
| permission: ShieldCheck, | ||
| copy: Copy, | ||
| delete: Trash2, | ||
| run: Play, | ||
| search: Search, | ||
| open: ExternalLink, | ||
| link: LinkIcon, | ||
| ai: Sparkles, | ||
| }; | ||
|
|
||
| export type TableActionIconName = keyof typeof tableActionIconMap; | ||
|
|
||
| export function TableActionIcon({ name }: { name: TableActionIconName }) { | ||
| const Icon = tableActionIconMap[name]; | ||
| return <Icon className='fc-table-action-menu-icon' />; | ||
| } | ||
|
|
||
| interface TableActionButtonProps extends Omit<ButtonProps, 'icon'> { | ||
| actionIcon?: TableActionIconName; | ||
| icon?: React.ReactNode; | ||
| } | ||
|
|
||
| export function TableActionButton({ actionIcon, icon, className, type = 'link', ...rest }: TableActionButtonProps) { | ||
| return ( | ||
| <Button | ||
| type={type} | ||
| className={classNames('fc-table-action-menu-button', className)} | ||
| icon={icon || (actionIcon ? <TableActionIcon name={actionIcon} /> : undefined)} | ||
| {...rest} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| interface TableActionLinkProps extends LinkProps { | ||
| actionIcon?: TableActionIconName; | ||
| } | ||
|
|
||
| export function TableActionLink({ actionIcon, className, children, ...rest }: TableActionLinkProps) { | ||
| return ( | ||
| <Link className={classNames('fc-table-action-menu-link', className)} {...rest}> | ||
| {actionIcon && <TableActionIcon name={actionIcon} />} | ||
| <span>{children}</span> | ||
| </Link> | ||
| ); | ||
| } | ||
|
|
||
| export const TableActionTrigger = React.forwardRef<HTMLElement, ButtonProps>(function TableActionTrigger({ type = 'text', icon, ...rest }, ref) { | ||
| return <Button ref={ref as any} type={type} icon={icon || <MoreVertical size={16} />} {...rest} />; | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| /// <reference types="jest" /> | ||
|
|
||
| // mock 掉有副作用或 ESM 专属语法的依赖,只测纯逻辑 | ||
| jest.mock('@/utils', () => ({ copy2ClipBoard: jest.fn() })); | ||
| jest.mock('react', () => ({})); | ||
| jest.mock('antd', () => ({})); | ||
| jest.mock('@ant-design/icons', () => ({})); | ||
| jest.mock('react-i18next', () => ({})); | ||
|
|
||
| import { calcLayout } from './Tags'; | ||
|
|
||
| // GAP = 2(与源码保持一致) | ||
|
|
||
| describe('calcLayout', () => { | ||
| // ────────────────────── 边界情况 ────────────────────── | ||
|
|
||
| it('空数组时返回 visibleCount=0, overflowCount=0', () => { | ||
| expect(calcLayout([], 40, 200)).toEqual({ visibleCount: 0, overflowCount: 0 }); | ||
| }); | ||
|
|
||
| it('containerWidth <= 0 时直接返回所有 tag 可见', () => { | ||
| expect(calcLayout([50, 60, 70], 40, 0)).toEqual({ visibleCount: 3, overflowCount: 0 }); | ||
| expect(calcLayout([50, 60, 70], 40, -1)).toEqual({ visibleCount: 3, overflowCount: 0 }); | ||
| }); | ||
|
|
||
| it('单个 tag 且恰好填满第一行', () => { | ||
| expect(calcLayout([100], 30, 100)).toEqual({ visibleCount: 1, overflowCount: 0 }); | ||
| }); | ||
|
|
||
| it('单个 tag 且宽度被容器宽度截断后仍可放入第一行', () => { | ||
| // 宽 300 > 容器 200,clamp 后为 200,恰好放入 | ||
| expect(calcLayout([300], 30, 200)).toEqual({ visibleCount: 1, overflowCount: 0 }); | ||
| }); | ||
|
|
||
| // ────────────────────── 第一行全部放下 ────────────────────── | ||
|
|
||
| it('所有 tag 均可放入第一行(宽松容器)', () => { | ||
| // 50 + 2+60 + 2+70 = 184 <= 300 | ||
| expect(calcLayout([50, 60, 70], 40, 300)).toEqual({ visibleCount: 3, overflowCount: 0 }); | ||
| }); | ||
|
|
||
| it('所有 tag 恰好填满第一行(边界等于)', () => { | ||
| // 100 + (2+48) = 150 | ||
| expect(calcLayout([100, 48], 30, 150)).toEqual({ visibleCount: 2, overflowCount: 0 }); | ||
| }); | ||
|
|
||
| it('只有一个 tag 放入第一行,其余全部溢出(无法放入第二行)', () => { | ||
| // 容器 120;第一行:100 → rem=20 | ||
| // 第二行 r2start=1:tag2=90 (isFirst,isLast); rem(120)>=90 → r2count=1 | ||
| // visibleCount=2, overflowCount=0 | ||
| expect(calcLayout([100, 90], 30, 120)).toEqual({ visibleCount: 2, overflowCount: 0 }); | ||
| }); | ||
|
|
||
| // ────────────────────── 两行布局(有溢出指示器)────────────────────── | ||
|
|
||
| it('第二行放入部分 tag,剩余作为溢出', () => { | ||
| // 容器 120, overflowTag=30 | ||
| // 第一行:50(rem=70) + 2+50=52(rem=18) → row1End=1;第三个放不下 | ||
| // 第二行 r2start=2: | ||
| // i=2(isFirst, not isLast): needed=50, 需 50+2+30=82 <= 120 ✓ → rem=70, r2count=1 | ||
| // i=3(not isFirst, not isLast): needed=2+50=52, 需 52+2+30=84 <= 70? ✗ → break | ||
| // visibleCount=3, overflowCount=4 | ||
| expect(calcLayout([50, 50, 50, 50, 50, 50, 50], 30, 120)).toEqual({ visibleCount: 3, overflowCount: 4 }); | ||
| }); | ||
|
|
||
| it('第二行最后一个 tag 恰好能放入(isLast 分支)', () => { | ||
| // 容器 100, overflowTag=30 | ||
| // 第一行:50(rem=50);2+60=62 > 50 → row1End=0 | ||
| // 第二行 r2start=1: | ||
| // i=1(isFirst, not isLast): needed=60, 需 60+2+30=92 <= 100 ✓ → rem=40, r2count=1 | ||
| // i=2(not isFirst, isLast): needed=2+30=32 <= 40 ✓ → r2count=2, break | ||
| // visibleCount=3, overflowCount=0 | ||
| expect(calcLayout([50, 60, 30], 30, 100)).toEqual({ visibleCount: 3, overflowCount: 0 }); | ||
| }); | ||
|
|
||
| it('第二行最后一个 tag 放不下(isLast 分支,空间不足)', () => { | ||
| // 容器 100, overflowTag=30 | ||
| // 第一行:50(rem=50);2+60=62 > 50 → row1End=0 | ||
| // 第二行 r2start=1: | ||
| // i=1(isFirst, not isLast): needed=60, 需 60+2+30=92 <= 100 ✓ → rem=40, r2count=1 | ||
| // i=2(not isFirst, isLast): needed=2+50=52 > 40 → 不放入, break | ||
| // visibleCount=2, overflowCount=1 | ||
| expect(calcLayout([50, 60, 50], 30, 100)).toEqual({ visibleCount: 2, overflowCount: 1 }); | ||
| }); | ||
|
|
||
| it('第二行第一个 tag 就放不下时一个也不放', () => { | ||
| // 容器 60, overflowTag=30 | ||
| // 第一行:50(rem=10);2+50=52 > 10 → row1End=0 | ||
| // 第二行 r2start=1: | ||
| // i=1(isFirst, not isLast): needed=50; 需 50+2+30=82 > 60 → break, r2count=0 | ||
| // visibleCount=1, overflowCount=2 | ||
| expect(calcLayout([50, 50, 50], 30, 60)).toEqual({ visibleCount: 1, overflowCount: 2 }); | ||
| }); | ||
|
|
||
| // ────────────────────── tag 宽度被截断 ────────────────────── | ||
|
|
||
| it('tag 宽度超过容器宽度时被截断为容器宽度', () => { | ||
| // tagWidths=[200,100], 容器 150 → widths=[150,100] | ||
| // 第一行:150(rem=0);2+100=102 > 0 → row1End=0 | ||
| // 第二行 r2start=1: | ||
| // i=1(isFirst, isLast): needed=100 <= 150 ✓ → r2count=1, break | ||
| // visibleCount=2, overflowCount=0 | ||
| expect(calcLayout([200, 100], 30, 150)).toEqual({ visibleCount: 2, overflowCount: 0 }); | ||
| }); | ||
|
|
||
| it('多个 tag 均超出容器宽度时均被截断', () => { | ||
| // 每个 tag 截断为 100,容器 100, overflowTag=30 | ||
| // 第一行:100(rem=0);2+100=102 > 0 → row1End=0 | ||
| // 第二行 r2start=1: | ||
| // i=1(isFirst, not isLast): needed=100; 需 100+2+30=132 > 100 → break | ||
| // visibleCount=1, overflowCount=2 | ||
| expect(calcLayout([200, 300, 400], 30, 100)).toEqual({ visibleCount: 1, overflowCount: 2 }); | ||
| }); | ||
|
|
||
| // ────────────────────── 第一行只放得下一个 tag ────────────────────── | ||
|
|
||
| it('第一行恰好放一个、第二行剩余全部放下(均为最后)', () => { | ||
| // 容器 80, overflowTag=20 | ||
| // 第一行:60(rem=20);2+60=62 > 20 → row1End=0 | ||
| // 第二行 r2start=1: | ||
| // i=1(isFirst, isLast): needed=60 <= 80 ✓ → r2count=1, break | ||
| // visibleCount=2, overflowCount=0 | ||
| expect(calcLayout([60, 60], 20, 80)).toEqual({ visibleCount: 2, overflowCount: 0 }); | ||
| }); | ||
|
|
||
| // ────────────────────── overflowTagWidth 影响 ────────────────────── | ||
|
|
||
| it('overflowTagWidth 较大时第二行能放入的 tag 更少', () => { | ||
| // 容器 200, overflowTag=100 | ||
| // 第一行:60(rem=140);2+60=62(rem=78);2+60=62 > 78? 78>=62 ✓(rem=16);2+60=62 > 16 → row1End=2 | ||
| // row1End=2 !== 4 → 进入第二行 | ||
| // 第二行 r2start=3: | ||
| // i=3(isFirst, not isLast): needed=60; 需 60+2+100=162 <= 200 ✓ → rem=140, r2count=1 | ||
| // i=4(not isFirst, isLast): needed=2+60=62 <= 140 ✓ → r2count=2, break | ||
| // visibleCount=5, overflowCount=0 | ||
| expect(calcLayout([60, 60, 60, 60, 60], 100, 200)).toEqual({ visibleCount: 5, overflowCount: 0 }); | ||
| }); | ||
|
|
||
| it('overflowTagWidth 较大导致第二行无法放入任何 tag', () => { | ||
| // 容器 80, overflowTag=80 | ||
| // 第一行:40(rem=40);2+40=42 > 40 → row1End=0 | ||
| // 第二行 r2start=1: | ||
| // i=1(isFirst, not isLast): needed=40; 需 40+2+80=122 > 80 → break | ||
| // visibleCount=1, overflowCount=2 | ||
| expect(calcLayout([40, 40, 40], 80, 80)).toEqual({ visibleCount: 1, overflowCount: 2 }); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix Stylelint keyword casing for sorter icon color.
Line 414 uses
currentColor, which violates the configuredvalue-keyword-caserule and will fail lint.Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 Stylelint (17.11.0)
[error] 414-414: Expected "currentColor" to be "currentcolor" (value-keyword-case)
(value-keyword-case)
🤖 Prompt for AI Agents