Skip to content

Commit f3c0e2c

Browse files
authored
Merge pull request #821 from objectstack-ai/copilot/optimize-ui-consistency
2 parents 4251dae + e5cb719 commit f3c0e2c

File tree

15 files changed

+168
-107
lines changed

15 files changed

+168
-107
lines changed

ROADMAP.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,45 @@ The `FlowDesigner` is a canvas-based flow editor that bridges the gap between th
781781
- [x] `exportOptions` schema reconciliation: Zod validator updated to accept both spec `string[]` format and ObjectUI object format via `z.union()`. ListView normalizes string[] to `{ formats }` at render time.
782782
- [x] `pagination.pageSizeOptions` backend integration: Page size selector is now a controlled component that dynamically updates `effectivePageSize`, triggering data re-fetch. `onPageSizeChange` callback fires on selection. Full test coverage for selector rendering, option enumeration, and data reload.
783783

784+
### P2.7 Platform UI Consistency & Interaction Optimization ✅
785+
786+
> All items from the UI consistency optimization (Issue #749) have been implemented.
787+
788+
**Global Theme & Design Tokens:**
789+
- [x] Hardcoded gray colors in `GridField.tsx`, `ReportRenderer.tsx`, and `ObjectGrid.tsx` replaced with theme tokens (`text-muted-foreground`, `bg-muted`, `border-border`, `border-foreground`)
790+
- [x] Global font-family (`Inter`, ui-sans-serif, system-ui) injected in `index.css` `:root`
791+
- [x] `--config-panel-width: 280px` CSS custom property added for unified config panel sizing
792+
- [x] Border radius standardized to `rounded-lg` across report/grid components
793+
- [x] `transition-colors duration-150` added to all interactive elements (toolbar buttons, tab bar, sidebar menu buttons)
794+
- [x] `LayoutRenderer.tsx` outer shell `bg-slate-50/50 dark:bg-zinc-950` replaced with `bg-background` theme token
795+
796+
**Sidebar Navigation:**
797+
- [x] `SidebarMenuButton` active state enhanced with 3px left indicator bar via `before:` pseudo-element
798+
- [x] `SidebarMenuButton` transition expanded to include `color, background-color` with `duration-150`
799+
- [x] `SidebarGroupLabel` visual separator added (`border-t border-border/30 pt-3 mt-2`)
800+
- [x] Collapsed-mode tooltip support in `SidebarNav` via `tooltip={item.title}` prop
801+
- [ ] `LayoutRenderer.tsx` hand-written sidebar → `SidebarNav` unification (deferred: requires extending SidebarNav to support nested menus, logo, version footer)
802+
803+
**ListView Toolbar:**
804+
- [x] Search changed from expandable button to always-visible inline `<Input>` (`w-48`)
805+
- [x] Activated state (`bg-primary/10 border border-primary/20`) added to Filter/Sort/Group/Color buttons when active
806+
- [x] Toolbar overflow improved with `overflow-x-auto` for responsive behavior
807+
- [x] `transition-colors duration-150` added to all toolbar buttons
808+
809+
**ObjectGrid Cell Renderers:**
810+
- [x] `formatRelativeDate()` function added for relative time display ("Today", "2 days ago", "Yesterday")
811+
- [x] DataTable/VirtualGrid header styling unified: `text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/70 bg-muted/30`
812+
- [x] Remaining hardcoded gray colors in ObjectGrid loading spinner and status badge fallback replaced with theme tokens
813+
814+
**ConfigPanelRenderer:**
815+
- [x] `<Separator>` added between sections for visual clarity
816+
- [x] Panel width uses `--config-panel-width` CSS custom property
817+
818+
**View Tab Bar:**
819+
- [x] Tab spacing tightened (`gap-0.5`, `px-3 py-1.5`)
820+
- [x] Active tab indicator changed to bottom border (`border-b-2 border-primary font-medium text-foreground`)
821+
- [x] `transition-colors duration-150` added to tab buttons
822+
784823
### P2.5 PWA & Offline (Real Sync)
785824

786825
- [ ] Background sync queue → real server sync (replace simulation)

packages/components/src/custom/config-panel-renderer.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { X, Save, RotateCcw, ChevronRight } from 'lucide-react';
1212

1313
import { cn } from '../lib/utils';
1414
import { Button } from '../ui/button';
15+
import { Separator } from '../ui/separator';
1516
import { SectionHeader } from './section-header';
1617
import { ConfigFieldRenderer } from './config-field-renderer';
1718
import type { ConfigPanelSchema } from '../types/config-panel';
@@ -128,7 +129,7 @@ export function ConfigPanelRenderer({
128129
aria-label={ariaLabel}
129130
tabIndex={tabIndex}
130131
className={cn(
131-
'absolute inset-y-0 right-0 w-full sm:w-72 lg:w-80 sm:relative border-l bg-background flex flex-col shrink-0 z-20',
132+
'absolute inset-y-0 right-0 w-full sm:w-[var(--config-panel-width,280px)] sm:relative border-l bg-background flex flex-col shrink-0 z-20',
132133
className,
133134
)}
134135
>
@@ -167,13 +168,14 @@ export function ConfigPanelRenderer({
167168

168169
{/* ── Scrollable sections ────────────────────────────── */}
169170
<div className="flex-1 overflow-auto px-4 pb-4">
170-
{schema.sections.map((section) => {
171+
{schema.sections.map((section, sectionIdx) => {
171172
if (section.visibleWhen && !section.visibleWhen(draft)) return null;
172173

173174
const sectionCollapsed = isCollapsed(section.key, section.defaultCollapsed);
174175

175176
return (
176177
<div key={section.key} data-testid={`config-section-${section.key}`}>
178+
{sectionIdx > 0 && <Separator className="my-1" />}
177179
<SectionHeader
178180
title={section.title}
179181
collapsible={section.collapsible}

packages/components/src/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,15 @@
7474

7575
--radius: 0.5rem;
7676

77+
--config-panel-width: 280px;
78+
7779
--chart-1: 12 76% 61%;
7880
--chart-2: 173 58% 39%;
7981
--chart-3: 197 37% 24%;
8082
--chart-4: 43 74% 66%;
8183
--chart-5: 27 87% 67%;
84+
85+
font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
8286
}
8387

8488
.dark {

packages/components/src/renderers/complex/data-table.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -624,18 +624,18 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
624624
<div className="rounded-md border flex-1 min-h-0 overflow-auto relative bg-background [-webkit-overflow-scrolling:touch] shadow-[inset_-8px_0_8px_-8px_rgba(0,0,0,0.08)]">
625625
<Table>
626626
{caption && <TableCaption>{caption}</TableCaption>}
627-
<TableHeader className="sticky top-0 bg-background z-10 shadow-sm">
627+
<TableHeader className="sticky top-0 bg-muted/30 z-10">
628628
<TableRow>
629629
{selectable && (
630-
<TableHead className={cn("w-12 bg-background", frozenColumns > 0 && "sticky left-0 z-20")}>
630+
<TableHead className={cn("w-12 bg-muted/30", frozenColumns > 0 && "sticky left-0 z-20")}>
631631
<Checkbox
632632
checked={allPageRowsSelected ? true : somePageRowsSelected ? 'indeterminate' : false}
633633
onCheckedChange={handleSelectAll}
634634
/>
635635
</TableHead>
636636
)}
637637
{showRowNumbers && (
638-
<TableHead className={cn("w-12 bg-background text-center", frozenColumns > 0 && "sticky z-20")} style={frozenColumns > 0 ? { left: selectable ? 48 : 0 } : undefined}>
638+
<TableHead className={cn("w-12 bg-muted/30 text-center", frozenColumns > 0 && "sticky z-20")} style={frozenColumns > 0 ? { left: selectable ? 48 : 0 } : undefined}>
639639
<span className="text-xs text-muted-foreground">#</span>
640640
</TableHead>
641641
)}
@@ -664,7 +664,7 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
664664
isDragOver && 'border-l-2 border-primary',
665665
col.align === 'right' && 'text-right',
666666
col.align === 'center' && 'text-center',
667-
'relative group bg-background',
667+
'relative group bg-muted/30',
668668
isFrozen && 'sticky z-20',
669669
isFrozen && index === frozenColumns - 1 && 'border-r-2 border-border shadow-[2px_0_4px_-2px_rgba(0,0,0,0.1)]',
670670
)}
@@ -692,7 +692,7 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
692692
{col.headerIcon && (
693693
<span className="text-muted-foreground flex-shrink-0">{col.headerIcon}</span>
694694
)}
695-
<span>{col.header}</span>
695+
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/70">{col.header}</span>
696696
{sortable && col.sortable !== false && getSortIcon(col.accessorKey)}
697697
</div>
698698
{resizableColumns && col.resizable !== false && (
@@ -707,7 +707,7 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
707707
);
708708
})}
709709
{rowActions && (
710-
<TableHead className="w-24 text-right bg-background">Actions</TableHead>
710+
<TableHead className="w-24 text-right bg-muted/30">Actions</TableHead>
711711
)}
712712
</TableRow>
713713
</TableHeader>

packages/components/src/ui/sidebar.tsx

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/fields/src/index.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,32 @@ export function formatPercent(value: number, precision: number = 0): string {
104104
return `${displayValue.toFixed(precision)}%`;
105105
}
106106

107+
/**
108+
* Format date as relative time (e.g., "2 days ago", "Today", "Overdue 3d")
109+
*/
110+
export function formatRelativeDate(value: string | Date): string {
111+
if (!value) return '-';
112+
const date = typeof value === 'string' ? new Date(value) : value;
113+
if (isNaN(date.getTime())) return '-';
114+
115+
const now = new Date();
116+
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
117+
const startOfDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
118+
const diffMs = startOfDate.getTime() - startOfToday.getTime();
119+
const diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24));
120+
121+
if (diffDays === 0) return 'Today';
122+
if (diffDays === 1) return 'Tomorrow';
123+
if (diffDays === -1) return 'Yesterday';
124+
if (diffDays < -1) {
125+
const absDays = Math.abs(diffDays);
126+
if (absDays <= 30) return `${absDays} days ago`;
127+
return formatDate(date);
128+
}
129+
if (diffDays > 1 && diffDays <= 30) return `In ${diffDays} days`;
130+
return formatDate(date);
131+
}
132+
107133
/**
108134
* Format date value
109135
*/
@@ -119,6 +145,10 @@ export function formatDate(value: string | Date, style?: string): string {
119145
const year = String(date.getFullYear()).slice(-2);
120146
return `${month} ${day}, '${year}`;
121147
}
148+
149+
if (style === 'relative') {
150+
return formatRelativeDate(date);
151+
}
122152

123153
// Default format: MMM DD, YYYY
124154
return date.toLocaleDateString('en-US', {

packages/fields/src/widgets/GridField.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,39 @@ export function GridField({ value, field, readonly, ...props }: FieldWidgetProps
1212
const columns = gridField?.columns || [];
1313

1414
if (!value || !Array.isArray(value)) {
15-
return <span className="text-sm text-gray-500">-</span>;
15+
return <span className="text-sm text-muted-foreground">-</span>;
1616
}
1717

1818
if (readonly) {
1919
return (
2020
<div className={cn("text-sm", props.className)}>
21-
<span className="text-gray-700">{value.length} rows</span>
21+
<span className="text-foreground">{value.length} rows</span>
2222
</div>
2323
);
2424
}
2525

2626
// Simple read-only table view
2727
return (
28-
<div className={cn("border border-gray-200 rounded-md overflow-hidden", props.className)}>
28+
<div className={cn("border border-border rounded-lg overflow-hidden", props.className)}>
2929
<div className="overflow-auto max-h-60">
3030
<table className="w-full text-sm">
31-
<thead className="bg-gray-50 border-b border-gray-200">
31+
<thead className="bg-muted border-b border-border">
3232
<tr>
3333
{columns.map((col: any, idx: number) => (
3434
<th
3535
key={idx}
36-
className="px-3 py-2 text-left text-xs font-medium text-gray-700"
36+
className="px-3 py-2 text-left text-xs font-medium text-muted-foreground"
3737
>
3838
{col.label || col.name}
3939
</th>
4040
))}
4141
</tr>
4242
</thead>
43-
<tbody className="divide-y divide-gray-200">
43+
<tbody className="divide-y divide-border">
4444
{value.slice(0, 5).map((row: any, rowIdx: number) => (
45-
<tr key={rowIdx} className="hover:bg-gray-50">
45+
<tr key={rowIdx} className="hover:bg-muted/50 transition-colors">
4646
{columns.map((col: any, colIdx: number) => (
47-
<td key={colIdx} className="px-3 py-2 text-gray-900">
47+
<td key={colIdx} className="px-3 py-2 text-foreground">
4848
{row[col.name] != null ? String(row[col.name]) : '-'}
4949
</td>
5050
))}
@@ -54,7 +54,7 @@ export function GridField({ value, field, readonly, ...props }: FieldWidgetProps
5454
</table>
5555
</div>
5656
{value.length > 5 && (
57-
<div className="bg-gray-50 px-3 py-2 text-xs text-gray-500 border-t border-gray-200">
57+
<div className="bg-muted px-3 py-2 text-xs text-muted-foreground border-t border-border">
5858
Showing 5 of {value.length} rows
5959
</div>
6060
)}

packages/layout/src/SidebarNav.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function SidebarNav({ items, title = "Application", className, collapsibl
3636
<SidebarMenu>
3737
{items.map((item) => (
3838
<SidebarMenuItem key={item.href}>
39-
<SidebarMenuButton asChild isActive={location.pathname === item.href}>
39+
<SidebarMenuButton asChild isActive={location.pathname === item.href} tooltip={item.title}>
4040
<NavLink to={item.href}>
4141
{item.icon && <item.icon />}
4242
<span>{item.title}</span>

packages/plugin-grid/src/ObjectGrid.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -708,8 +708,8 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
708708
}
709709
return (
710710
<div className="p-4 sm:p-8 text-center">
711-
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
712-
<p className="mt-2 text-sm text-gray-600">Loading grid...</p>
711+
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-foreground"></div>
712+
<p className="mt-2 text-sm text-muted-foreground">Loading grid...</p>
713713
</div>
714714
);
715715
}
@@ -909,7 +909,7 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
909909
return 'bg-indigo-100 text-indigo-800 border-indigo-300';
910910
if (v.includes('prospecting') || v.includes('new') || v.includes('open'))
911911
return 'bg-purple-100 text-purple-800 border-purple-300';
912-
return 'bg-gray-100 text-gray-800 border-gray-300';
912+
return 'bg-muted text-muted-foreground border-border';
913913
};
914914

915915
// Left border color for card accent based on stage

packages/plugin-grid/src/VirtualGrid.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const VirtualGrid: React.FC<VirtualGridProps> = ({
8484
<div className={className}>
8585
{/* Header */}
8686
<div
87-
className={`grid border-b sticky top-0 bg-background z-10 ${headerClassName}`}
87+
className={`grid border-b sticky top-0 bg-muted/30 z-10 ${headerClassName}`}
8888
style={{
8989
gridTemplateColumns: columns
9090
.map((col) => col.width || '1fr')
@@ -94,7 +94,7 @@ export const VirtualGrid: React.FC<VirtualGridProps> = ({
9494
{columns.map((column, index) => (
9595
<div
9696
key={index}
97-
className={`px-4 py-2 font-semibold text-sm ${
97+
className={`px-4 py-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/70 ${
9898
column.align === 'center'
9999
? 'text-center'
100100
: column.align === 'right'

0 commit comments

Comments
 (0)