Optimize ViewConfigPanel to align with Airtable property panel UX#690
Optimize ViewConfigPanel to align with Airtable property panel UX#690
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…ible sections, new appearance/user-action fields, breadcrumb header, and i18n for all 11 locales Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…dcrumb, collapsible sections, appearance and user actions fields; update ROADMAP.md Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…height buttons for accessibility Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Restructures the Console’s ViewConfigPanel into an Airtable-style property panel with breadcrumb header, collapsible sections, compact summary rows, and expanded appearance/user-action controls, plus corresponding i18n and test updates.
Changes:
- Refactors
ViewConfigPanelUI into collapsible Data/Appearance/User Actions sections with expandable Sort/Filter/Fields sub-rows and a breadcrumb header. - Adds new config controls (appearance + user actions) and calendar
endDateFieldselection. - Updates i18n across locales, expands/updates tests, and marks the work completed in the roadmap.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/console/src/components/ViewConfigPanel.tsx | Implements the new Airtable-style layout, collapsible sections, and new config controls. |
| apps/console/src/tests/ViewConfigPanel.test.tsx | Updates existing tests for the new expandable pattern and adds new coverage for breadcrumb/sections/new fields. |
| packages/i18n/src/locales/en.ts | Adds new translation keys used by the redesigned panel. |
| packages/i18n/src/locales/zh.ts | Backfills/adds new translation keys for redesigned panel. |
| packages/i18n/src/locales/ja.ts | Backfills/adds new translation keys for redesigned panel. |
| packages/i18n/src/locales/de.ts | Backfills/adds new translation keys for redesigned panel. |
| packages/i18n/src/locales/fr.ts | Backfills/adds new translation keys for redesigned panel. |
| packages/i18n/src/locales/es.ts | Backfills/adds new translation keys for redesigned panel. |
| packages/i18n/src/locales/ar.ts | Backfills/adds new translation keys for redesigned panel. |
| packages/i18n/src/locales/ru.ts | Backfills/adds new translation keys for redesigned panel. |
| packages/i18n/src/locales/pt.ts | Backfills/adds new translation keys for redesigned panel. |
| packages/i18n/src/locales/ko.ts | Backfills/adds new translation keys for redesigned panel. |
| ROADMAP.md | Marks the newly delivered panel UX items as completed. |
| updateDraft('groupBy', e.target.value); | ||
| if (viewType === 'kanban') { | ||
| handleTypeOptionChange('kanban', 'groupByField', e.target.value); |
There was a problem hiding this comment.
The "Group by" selector writes a flat groupBy property and (for kanban) updates kanban.groupByField, but the runtime view schema builders read options.kanban.groupField (and warn on flat keys in dev). This means changing Group by in the panel may not actually change kanban grouping and can trigger spec-compliance warnings. Consider removing the flat groupBy write and updating kanban.groupField (optionally mirroring to kanban.groupByField for backward compatibility) instead.
| updateDraft('groupBy', e.target.value); | |
| if (viewType === 'kanban') { | |
| handleTypeOptionChange('kanban', 'groupByField', e.target.value); | |
| const value = e.target.value; | |
| if (viewType === 'kanban') { | |
| // For kanban, use options.kanban.groupField as the canonical key | |
| handleTypeOptionChange('kanban', 'groupField', value); | |
| // Mirror to groupByField for backward compatibility | |
| handleTypeOptionChange('kanban', 'groupByField', value); | |
| } else { | |
| // Preserve existing flat groupBy behavior for non-kanban views | |
| updateDraft('groupBy', value); |
| value={draft.kanban?.groupByField || draft.kanban?.groupField || ''} | ||
| onChange={(e: React.ChangeEvent<HTMLSelectElement>) => handleTypeOptionChange('kanban', 'groupByField', e.target.value)} |
There was a problem hiding this comment.
For kanban, the panel renders both the universal "Group by" row and a type-specific "Group by field" select. These controls can diverge (type-specific updates only kanban.groupByField), and it’s redundant UX. Suggest keeping a single source of truth (either remove the type-specific row for kanban or have both update the same underlying key(s), e.g. kanban.groupField).
| value={draft.kanban?.groupByField || draft.kanban?.groupField || ''} | |
| onChange={(e: React.ChangeEvent<HTMLSelectElement>) => handleTypeOptionChange('kanban', 'groupByField', e.target.value)} | |
| value={draft.kanban?.groupField || draft.kanban?.groupByField || ''} | |
| onChange={(e: React.ChangeEvent<HTMLSelectElement>) => { | |
| const value = e.target.value; | |
| handleTypeOptionChange('kanban', 'groupField', value); | |
| handleTypeOptionChange('kanban', 'groupByField', value); | |
| }} |
| checked={hasExport} | ||
| onCheckedChange={(checked: boolean) => updateDraft('allowExport', checked)} |
There was a problem hiding this comment.
The export toggle’s checked state is derived from hasExport, which is true when draft.exportOptions is defined. If exportOptions is set, toggling "Allow export" off only updates allowExport but the switch will remain on (and export remains enabled) because exportOptions is still present. Consider making the switch state depend on allowExport (and/or clearing exportOptions when disabling) so the control can actually turn export off.
| checked={hasExport} | |
| onCheckedChange={(checked: boolean) => updateDraft('allowExport', checked)} | |
| checked={draft.allowExport !== false && hasExport} | |
| onCheckedChange={(checked: boolean) => { | |
| updateDraft('allowExport', checked); | |
| if (!checked) { | |
| updateDraft('exportOptions', undefined); | |
| } | |
| }} |
| <div className="flex gap-0.5" data-testid="appearance-rowHeight" role="radiogroup" aria-label={t('console.objectView.rowHeight')}> | ||
| {ROW_HEIGHT_OPTIONS.map(opt => ( | ||
| <button | ||
| key={opt.value} | ||
| type="button" | ||
| role="radio" | ||
| aria-checked={(draft.rowHeight || 'short') === opt.value} | ||
| aria-label={opt.value} | ||
| data-testid={`row-height-${opt.value}`} | ||
| className={`h-7 w-7 rounded border flex items-center justify-center ${ | ||
| (draft.rowHeight || 'short') === opt.value | ||
| ? 'border-primary bg-primary/10 text-primary' | ||
| : 'border-input text-muted-foreground hover:bg-accent/50' | ||
| }`} | ||
| onClick={() => updateDraft('rowHeight', opt.value)} | ||
| title={opt.value} | ||
| > | ||
| <div className={`flex flex-col items-center justify-center w-4 h-4 ${opt.gapClass}`}> | ||
| <div className="w-3.5 h-px bg-current rounded-full" /> | ||
| <div className="w-3.5 h-px bg-current rounded-full" /> | ||
| <div className="w-3.5 h-px bg-current rounded-full" /> | ||
| </div> | ||
| </button> | ||
| ))} |
There was a problem hiding this comment.
The row height control uses role="radiogroup"/role="radio" but doesn’t implement the expected roving tabIndex/arrow-key keyboard interaction, and each radio’s aria-label is the raw internal value ("short", "medium", etc.) rather than a localized label. Consider using native radio inputs or adding keyboard handling + localized labels to match ARIA radio patterns.
| <div className="flex gap-0.5" data-testid="appearance-rowHeight" role="radiogroup" aria-label={t('console.objectView.rowHeight')}> | |
| {ROW_HEIGHT_OPTIONS.map(opt => ( | |
| <button | |
| key={opt.value} | |
| type="button" | |
| role="radio" | |
| aria-checked={(draft.rowHeight || 'short') === opt.value} | |
| aria-label={opt.value} | |
| data-testid={`row-height-${opt.value}`} | |
| className={`h-7 w-7 rounded border flex items-center justify-center ${ | |
| (draft.rowHeight || 'short') === opt.value | |
| ? 'border-primary bg-primary/10 text-primary' | |
| : 'border-input text-muted-foreground hover:bg-accent/50' | |
| }`} | |
| onClick={() => updateDraft('rowHeight', opt.value)} | |
| title={opt.value} | |
| > | |
| <div className={`flex flex-col items-center justify-center w-4 h-4 ${opt.gapClass}`}> | |
| <div className="w-3.5 h-px bg-current rounded-full" /> | |
| <div className="w-3.5 h-px bg-current rounded-full" /> | |
| <div className="w-3.5 h-px bg-current rounded-full" /> | |
| </div> | |
| </button> | |
| ))} | |
| <div | |
| className="flex gap-0.5" | |
| data-testid="appearance-rowHeight" | |
| aria-label={t('console.objectView.rowHeight')} | |
| > | |
| {ROW_HEIGHT_OPTIONS.map(opt => { | |
| const isSelected = (draft.rowHeight || 'short') === opt.value; | |
| const label = t(`console.objectView.rowHeight.${opt.value}`); | |
| return ( | |
| <label | |
| key={opt.value} | |
| className={`h-7 w-7 rounded border flex items-center justify-center cursor-pointer ${ | |
| isSelected | |
| ? 'border-primary bg-primary/10 text-primary' | |
| : 'border-input text-muted-foreground hover:bg-accent/50' | |
| }`} | |
| title={label} | |
| > | |
| <input | |
| type="radio" | |
| name="rowHeight" | |
| value={opt.value} | |
| checked={isSelected} | |
| onChange={() => updateDraft('rowHeight', opt.value)} | |
| aria-label={label} | |
| className="sr-only" | |
| /> | |
| <div | |
| className={`flex flex-col items-center justify-center w-4 h-4 ${opt.gapClass}`} | |
| data-testid={`row-height-${opt.value}`} | |
| > | |
| <div className="w-3.5 h-px bg-current rounded-full" /> | |
| <div className="w-3.5 h-px bg-current rounded-full" /> | |
| <div className="w-3.5 h-px bg-current rounded-full" /> | |
| </div> | |
| </label> | |
| ); | |
| })} |
Restructures the right-side ViewConfigPanel from a flat form layout to an Airtable-style property panel with hierarchical navigation, collapsible sections, compact summary rows, and expanded appearance/user-action controls.
Panel header
Page › List/Page › Kanbanetc., replacing generic "Configure View" titleCollapsible sections
aria-expandedData section restructured
kanban.groupByField)endDateFieldsupportAppearance section
New fields matching Airtable: Color, Field text color, Row height (4 icon buttons as
role="radiogroup"), Wrap headers, Show field descriptions, Collapse all by defaultUser actions section
New toggles: Edit records inline, Add/delete records inline, Click into record details. Existing Add record via form and Allow export consolidated here (removed standalone "Advanced" section).
i18n
17 new keys added to all 11 locales (en, zh, ja, de, fr, es, ar, ru, pt, ko). Missing keys (createView, typeOptions, etc.) backfilled for non-English locales.
Tests
62 tests passing — 11 existing tests updated for new expandable sub-row pattern, 13 new tests covering breadcrumb, section collapse, appearance fields, user actions, calendar endDateField, and data sub-section toggle.
Original prompt
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.