feat(apollo-wind): scrollable tabs + per-tab error badges in tabbed MetadataForm#922
feat(apollo-wind): scrollable tabs + per-tab error badges in tabbed MetadataForm#922CalinaCristian wants to merge 2 commits into
Conversation
…etadataForm Adapt the tabbed properties-panel form to narrow panels: - TabbedStepForm now renders its tab strip with ScrollableTabsList, so overflowing tabs get prev/next chevrons and the active tab auto-scrolls into view. Wide panels are unaffected (no chevrons when nothing overflows). - Each tab shows a count badge of its fields that currently have errors, computed from react-hook-form state via useFormState. Fields on inactive tabs are counted too, so a hidden validation problem still surfaces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Dependency License Review
License distribution
Excluded packages
|
📦 Dev Packages
|
There was a problem hiding this comment.
Pull request overview
This PR improves the tabbed MetadataForm experience in narrow properties panels by making the tab strip horizontally scrollable (with chevrons when overflowed) and by surfacing validation state per tab via an error-count badge.
Changes:
- Replaced the plain
TabsListwithScrollableTabsListinTabbedStepFormto support overflow scrolling and auto-reveal of the active tab. - Added per-tab error count badges computed from
react-hook-formstate (viauseFormState) so errors on inactive tabs are still visible. - Added a unit test asserting a badge appears on an inactive tab with a seeded field error.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/apollo-wind/src/components/forms/metadata-form.tsx | Adds ScrollableTabsList tab strip and computes per-step error counts from RHF state to render badges. |
| packages/apollo-wind/src/components/forms/metadata-form.test.tsx | Adds a test to verify an inactive tab shows an error-count badge when an error is present. |
| const errorCountByStepId = useMemo(() => { | ||
| const counts: Record<string, number> = {}; | ||
| for (const step of visibleSteps) { | ||
| let count = 0; | ||
| for (const section of step.sections) { | ||
| if (section.conditions && !context.evaluateConditions(section.conditions)) { | ||
| continue; | ||
| } | ||
| for (const field of section.fields) { | ||
| if (context.form.getFieldState(field.name, formState).error) count++; | ||
| } | ||
| } | ||
| counts[step.id] = count; | ||
| } | ||
| return counts; | ||
| // `formState` is the reactive dependency; `context` is stable per render. | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [visibleSteps, formState, context]); |
| <TabsTrigger | ||
| key={step.id} | ||
| value={step.id} | ||
| className="inline-flex h-6 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-md px-2.5 text-xs font-medium text-muted-foreground shadow-none transition-colors hover:text-foreground data-[state=active]:bg-surface-overlay data-[state=active]:text-foreground data-[state=active]:shadow-sm" | ||
| > | ||
| {step.title} | ||
| {errorCount > 0 && ( | ||
| <span | ||
| title={`${errorCount} ${errorCount === 1 ? 'issue' : 'issues'}`} | ||
| className="grid h-4 min-w-4 place-items-center rounded-full bg-error px-1 text-[10px] font-semibold leading-none text-foreground-on-accent" | ||
| > | ||
| {errorCount} | ||
| </span> | ||
| )} | ||
| </TabsTrigger> |
📊 Coverage + size by packagePer-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.
"Coverage" is each package's own |
A composite field (e.g. a connector editor holding many sub-fields) surfaces several validation issues at once, stored as an array of errors under one field name. Count each array entry so the tab badge reflects the number of issues the user sees inside the field instead of collapsing the whole field to "1". Plain fields (a single error object) still count as 1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| <TabsTrigger | ||
| key={step.id} | ||
| value={step.id} | ||
| className="inline-flex h-6 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-md px-2.5 text-xs font-medium text-muted-foreground shadow-none transition-colors hover:text-foreground data-[state=active]:bg-surface-overlay data-[state=active]:text-foreground data-[state=active]:shadow-sm" | ||
| > |
| onFormInit: (context) => { | ||
| context.form.setError('field1.0', { message: 'Channel is required' }); | ||
| context.form.setError('field1.1', { | ||
| message: 'Timestamp is required', | ||
| }); | ||
| context.form.setError('field1.2', { message: 'Message is required' }); | ||
| }, |
What
Adapts the tabbed
MetadataForm(properties-panel form) so it holds up in a narrow panel and surfaces validation state on the tab strip.TabbedStepFormnow renders its tab strip withScrollableTabsListinstead of a plainTabsList. When the tabs overflow a narrow panel they get prev/next chevrons and the active tab auto-scrolls into view. Wide panels are unchanged (no chevrons render when nothing overflows).useFormState(so it updates live). Fields on inactive tabs are counted too, so a hidden validation problem still surfaces on its tab.Why
Consumed by
flow-workbench's redesigned node properties panel, which docks narrow. Today the built-in tab strip clips overflowing tabs with no affordance, and validation errors on non-active tabs are invisible. Both fixes live here because the tab strip is owned byTabbedStepForm; the consumer drives it purely throughschema.steps+ form plugins, so no consumer-side change is needed (errors are already in form state via the consumer's validation plugin).Testing
pnpm --filter @uipath/apollo-wind test— 986 pass, incl. a new test asserting a badge on an errored (inactive) tab.Notes
dev-packageslabel added to publish a dev build forflow-workbenchto consume.🤖 Generated with Claude Code