Skip to content

fix(ui-core): unblock antd migration — optional Alert title, dashed Divider, disabled-trigger Tooltip - #33163

Draft
chirag-madlani wants to merge 3 commits into
mainfrom
fix/core-component-gaps
Draft

fix(ui-core): unblock antd migration — optional Alert title, dashed Divider, disabled-trigger Tooltip#33163
chirag-madlani wants to merge 3 commits into
mainfrom
fix/core-component-gaps

Conversation

@chirag-madlani

Copy link
Copy Markdown
Collaborator

Three gaps in ui-core-components that are each blocking antd call sites from migrating. Separate commits, independently reviewable.

Found while sweeping Collate — every one of these turned a would-be mechanical migration into "leave it on antd".

1. Alert — a body without a title

title was a required string. antd's message is an optional ReactNode, and its description-only alerts have no heading at all. Of Collate's 11 alerts, only 2 had a plain string message — 6 passed markup, and 3 had no message whatsoever. Migrating those meant inventing user-facing copy.

title is now optional and takes a ReactNode. When absent, the heading element isn't rendered and the alert uses the single-block layout it already had for a title with no body, so the icon stays centred instead of top-aligned against a lone paragraph. Every existing call site passes a title (it was required), so nothing changes for them.

2. Divider — dashed, and a vertical rule with a height

  • No dashed variant. A solid rule is a filled 1px box and a background can't be dashed, so the dashed case switches to a border and collapses the box in that axis. The rules either side of a label follow suit.
  • The vertical rule had no intrinsic height. It relied entirely on self-stretch, which produces nothing outside a flex/grid parent and is overridden the moment a consumer aligns the divider itself — an unlayered LESS .self-center is enough to collapse it to zero and make it vanish silently. antd's vertical divider had an intrinsic height; min-h-[1em] restores that without capping the stretch case.

Between them these unblock 21 Collate elements.

3. Tooltip — open on a disabled trigger

A disabled control fires no pointer or focus events, so react-aria never opened its tooltip. That's backwards for the usual case: the tooltip is what explains why the control is disabled, and it vanished exactly when the user needed it. antd wrapped disabled children in a listener element for this reason.

A disabled child now takes the same auto-wrapping path as a non-focusable one, but via Focusable + a span rather than the AriaButton wrapper — the child is already a button, and nesting one inside another is invalid HTML and breaks clicks. The child is made inert so it stops swallowing the events the wrapper needs.

Two Collate sites sit on antd purely because of this, and QueryToolbar has the same latent bug on main today.

Verification

  • 16 new tests (7 Alert, 6 Divider, 3 Tooltip); the Tooltip file's existing 12 still pass, so the new wrap path doesn't disturb the existing ones.
  • Full core suite 110/111. The single failure (Table › can omit the selection cell for a full-width synthetic row) is pre-existing on clean main — verified by stashing this branch and re-running.
  • tsc, eslint, prettier clean.

Note for reviewers

A fourth change was planned — making Button forward injected mouse handlers — and dropped after measuring. I'd assumed react-aria's filterDOMProps stripped them. It doesn't: onBlur, onMouseOver, onMouseDown and onPointerEnter all reach the DOM. Only onFocus is swallowed, which means an antd-wrapped core Button gets hover tooltips but not keyboard-focus ones. Real, but narrow, and not worth speculative surgery on Button's prop plumbing — raising separately if it's worth fixing.

Draft: happy to split further or adjust the Alert layout fallback if you'd rather it stayed top-aligned.

🤖 Generated with Claude Code

chirag-madlani and others added 3 commits September 11, 2026 00:12
`title` was a required `string`. antd's equivalent, `message`, is an
optional ReactNode, and its `description`-only alerts have no heading at
all - so migrating those meant either inventing user-facing copy or
leaving them on antd. Across Collate's 11 alerts only 2 had a plain
string message: 6 passed markup and 3 had no message whatsoever.

`title` is now optional and accepts a ReactNode. When it is absent the
heading element is not rendered, and the alert falls back to the
single-block layout it already used for a title with no body, so the
icon stays centred rather than top-aligned against a lone paragraph.
Existing call sites - which all pass a title, since it was required -
render exactly as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps that together blocked migrating antd's Divider:

- No dashed variant. A solid rule here is a filled 1px box, and a
  background cannot be dashed, so the dashed case switches to a border
  and collapses the box in that axis. The flanking rules either side of
  a label follow the same variant.

- The vertical rule expressed its height solely through `self-stretch`,
  which produces nothing outside a flex or grid parent and is overridden
  whenever a consumer aligns the divider itself - `self-center` on an
  unlayered LESS class is enough to collapse it to zero and make it
  vanish. antd's vertical divider had an intrinsic height for this
  reason; `min-h-[1em]` restores that without capping it when stretching
  does apply.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A disabled control fires no pointer or focus events, so react-aria never
opened the tooltip on one. That is backwards for the common case: the
tooltip is usually what explains *why* the control is disabled, and it
disappeared exactly when the user needed it. antd wrapped disabled
children in its own listener element to avoid this.

A disabled child now takes the same auto-wrapping path as a
non-focusable one, but through `Focusable` and a span rather than the
`AriaButton` wrapper - the child is already a button, and nesting one
inside another is invalid HTML and breaks clicks. The child is also made
inert so it stops swallowing the pointer events the wrapper needs.

Two Collate call sites are on antd purely because of this, and
QueryToolbar has the same latent bug today.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs UI UI specific issues labels Sep 10, 2026
Comment on lines +41 to +43
return axis === 'y'
? 'tw:w-0 tw:border-l tw:border-dashed tw:border-secondary'
: 'tw:h-0 tw:border-t tw:border-dashed tw:border-secondary';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: Dashed divider uses non-existent color token, renders wrong color

The dashed branch of ruleClasses sets the border color with tw:border-secondary, but no --color-secondary token exists — the token is border-secondary, so the color utility must be tw:border-border-secondary (mirroring the solid branch's tw:bg-border-secondary). As written, the border-color utility isn't generated and the dashed rule falls back to currentColor (the inherited text color), so every dashed divider — the whole point of this change — draws in the wrong, usually darker color. The tests only assert border-dashed/border-l, so they don't catch it.

Use the border-secondary color token via border-border-secondary.:

return axis === 'y'
  ? 'tw:w-0 tw:border-l tw:border-dashed tw:border-border-secondary'
  : 'tw:h-0 tw:border-t tw:border-dashed tw:border-border-secondary';
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

Comment on lines +159 to +168
<AriaFocusable>
<span
className={cx(
'tw:inline-flex tw:w-max tw:cursor-not-allowed tw:*:pointer-events-none',
triggerClassName
)}
tabIndex={0}>
{children}
</span>
</AriaFocusable>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Edge Case: Disabled-trigger wrap ignores excludeTriggerFromTabOrder

The disabled-child branch hardcodes tabIndex={0} on the wrapper span, so a disabled trigger that a consumer explicitly asked to exclude from the tab order (excludeTriggerFromTabOrder) is still keyboard-focusable, unlike the non-disabled wrap path which honors that flag. Consider deriving tabIndex from excludeTriggerFromTabOrder (e.g. tabIndex={excludeTriggerFromTabOrder ? -1 : 0}) for consistency.

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown
Code Review ⚠️ Changes requested 0 resolved / 2 findings

Unblocks antd migration by making Alert title optional, adding dashed Divider support, and opening Tooltip on disabled triggers. Two issues must be fixed: the dashed divider uses a non-existent color token (tw:border-secondary should be tw:border-border-secondary), causing it to render in the wrong color, and the disabled-trigger wrap hardcodes tabIndex={0} instead of respecting the excludeTriggerFromTabOrder flag.

⚠️ Bug: Dashed divider uses non-existent color token, renders wrong color

📄 openmetadata-ui-core-components/src/main/resources/ui/src/components/base/divider/divider.tsx:41-43

The dashed branch of ruleClasses sets the border color with tw:border-secondary, but no --color-secondary token exists — the token is border-secondary, so the color utility must be tw:border-border-secondary (mirroring the solid branch's tw:bg-border-secondary). As written, the border-color utility isn't generated and the dashed rule falls back to currentColor (the inherited text color), so every dashed divider — the whole point of this change — draws in the wrong, usually darker color. The tests only assert border-dashed/border-l, so they don't catch it.

Use the border-secondary color token via border-border-secondary.
return axis === 'y'
  ? 'tw:w-0 tw:border-l tw:border-dashed tw:border-border-secondary'
  : 'tw:h-0 tw:border-t tw:border-dashed tw:border-border-secondary';
💡 Edge Case: Disabled-trigger wrap ignores excludeTriggerFromTabOrder

📄 openmetadata-ui-core-components/src/main/resources/ui/src/components/base/tooltip/tooltip.tsx:159-168

The disabled-child branch hardcodes tabIndex={0} on the wrapper span, so a disabled trigger that a consumer explicitly asked to exclude from the tab order (excludeTriggerFromTabOrder) is still keyboard-focusable, unlike the non-disabled wrap path which honors that flag. Consider deriving tabIndex from excludeTriggerFromTabOrder (e.g. tabIndex={excludeTriggerFromTabOrder ? -1 : 0}) for consistency.

🤖 Prompt for agents
Code Review: Unblocks antd migration by making Alert title optional, adding dashed Divider support, and opening Tooltip on disabled triggers. Two issues must be fixed: the dashed divider uses a non-existent color token (`tw:border-secondary` should be `tw:border-border-secondary`), causing it to render in the wrong color, and the disabled-trigger wrap hardcodes `tabIndex={0}` instead of respecting the `excludeTriggerFromTabOrder` flag.

1. ⚠️ Bug: Dashed divider uses non-existent color token, renders wrong color
   Files: openmetadata-ui-core-components/src/main/resources/ui/src/components/base/divider/divider.tsx:41-43

   The dashed branch of `ruleClasses` sets the border color with `tw:border-secondary`, but no `--color-secondary` token exists — the token is `border-secondary`, so the color utility must be `tw:border-border-secondary` (mirroring the solid branch's `tw:bg-border-secondary`). As written, the border-color utility isn't generated and the dashed rule falls back to `currentColor` (the inherited text color), so every dashed divider — the whole point of this change — draws in the wrong, usually darker color. The tests only assert `border-dashed`/`border-l`, so they don't catch it.

   Fix (Use the border-secondary color token via border-border-secondary.):
   return axis === 'y'
     ? 'tw:w-0 tw:border-l tw:border-dashed tw:border-border-secondary'
     : 'tw:h-0 tw:border-t tw:border-dashed tw:border-border-secondary';

2. 💡 Edge Case: Disabled-trigger wrap ignores excludeTriggerFromTabOrder
   Files: openmetadata-ui-core-components/src/main/resources/ui/src/components/base/tooltip/tooltip.tsx:159-168

   The disabled-child branch hardcodes `tabIndex={0}` on the wrapper span, so a disabled trigger that a consumer explicitly asked to exclude from the tab order (`excludeTriggerFromTabOrder`) is still keyboard-focusable, unlike the non-disabled wrap path which honors that flag. Consider deriving tabIndex from `excludeTriggerFromTabOrder` (e.g. `tabIndex={excludeTriggerFromTabOrder ? -1 : 0}`) for consistency.

Options

Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs UI UI specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant