Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/app-shell/src/views/InterfaceListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,10 @@ export function InterfaceListPage({ page, className, onConfigChange, reserveEdit
showGroup: false,
showColor: false,
allowExport: false,
inlineEdit: false,
// Inline record editing is a page-authored property: a list block opts in
// via `userActions.editInline` (default off). When on, clicking a cell
// edits it with the dedicated field widgets, same as the object views.
inlineEdit: userActions.editInline === true,
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [objectDefName, viewDefJson, cfg]);
Expand Down
3 changes: 3 additions & 0 deletions packages/app-shell/src/views/ObjectView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1416,6 +1416,9 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }: an
onHiddenFieldsChange={(hidden: string[]) => {
persistViewPatch(viewDef.id, viewDef, { hiddenFields: hidden });
}}
onInlineEditChange={(next: boolean) => {
persistViewPatch(viewDef.id, viewDef, { inlineEdit: next });
}}
onColumnStateChange={(state: { order?: string[]; widths?: Record<string, number> }) => {
persistViewPatch(viewDef.id, viewDef, { columnState: state });
}}
Expand Down
26 changes: 25 additions & 1 deletion packages/plugin-list/src/ListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import * as React from 'react';
import { cn, Button, Input, Popover, PopoverContent, PopoverTrigger, FilterBuilder, SortBuilder, NavigationOverlay, GroupingEditor, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, RefreshIndicator, DataEmptyState } from '@object-ui/components';
import type { SortItem } from '@object-ui/components';
import { Search, SlidersHorizontal, ArrowUpDown, X, EyeOff, Group, Paintbrush, Ruler, Inbox, Download, AlignJustify, Rows4, Rows3, Rows2, Share2, Printer, Plus, Trash2, CheckSquare, AlertTriangle, RotateCw, icons, type LucideIcon } from 'lucide-react';
import { Search, SlidersHorizontal, ArrowUpDown, X, EyeOff, Pencil, Group, Paintbrush, Ruler, Inbox, Download, AlignJustify, Rows4, Rows3, Rows2, Share2, Printer, Plus, Trash2, CheckSquare, AlertTriangle, RotateCw, icons, type LucideIcon } from 'lucide-react';
import type { FilterGroup } from '@object-ui/components';
import { ViewSwitcherDropdown, ViewType } from './ViewSwitcher';
import { ViewSettingsPopover } from './components/ViewSettingsPopover';
Expand All @@ -31,6 +31,8 @@ export interface ListViewProps {
onSearchChange?: (search: string) => void;
/** Called when the user toggles fields via the Hide Fields popover. */
onHiddenFieldsChange?: (hidden: string[]) => void;
/** Called when the user toggles inline record editing in View settings. */
onInlineEditChange?: (next: boolean) => void;
/** Called when the user resizes/reorders columns in the underlying grid. */
onColumnStateChange?: (state: { order?: string[]; widths?: Record<string, number> }) => void;
/** Callback when a row/item is clicked (overrides NavigationConfig) */
Expand Down Expand Up @@ -322,6 +324,7 @@ export const ListView = React.forwardRef<ListViewHandle, ListViewProps>(({
onSortChange,
onSearchChange,
onHiddenFieldsChange,
onInlineEditChange,
onColumnStateChange,
onRowClick,
showViewSwitcher: showViewSwitcherProp,
Expand Down Expand Up @@ -1639,6 +1642,24 @@ export const ListView = React.forwardRef<ListViewHandle, ListViewProps>(({
<div className="h-4 w-px bg-border/60 mx-0.5" />
</>
)}
{/* Inline edit — toggle record editing for this (grid) view. Persists
`inlineEdit` on the view via onInlineEditChange. */}
{currentView === 'grid' && onInlineEditChange && !toolbarFlags.compactToolbar && (
<Button
variant="ghost"
size="sm"
onClick={() => onInlineEditChange(!schema.inlineEdit)}
className={cn(
"hidden sm:inline-flex h-7 px-2 text-muted-foreground hover:text-primary text-xs transition-colors duration-150",
schema.inlineEdit && "text-primary"
)}
title={t('list.inlineEditLabel', { defaultValue: 'Edit records inline (click a cell to edit)' })}
data-testid="toolbar-inline-edit-toggle"
>
<Pencil className="h-3.5 w-3.5 mr-1.5" />
<span className="hidden sm:inline">{t('list.inlineEditShort', { defaultValue: 'Edit inline' })}</span>
</Button>
)}
{/* Hide Fields — hidden on mobile (collapsed into ViewSettingsPopover) */}
{toolbarFlags.showHideFields && !toolbarFlags.compactToolbar && (
<Popover open={showHideFields} onOpenChange={setShowHideFields}>
Expand Down Expand Up @@ -1982,6 +2003,9 @@ export const ListView = React.forwardRef<ListViewHandle, ListViewProps>(({
showHideFields={toolbarFlags.showHideFields}
hiddenFields={hiddenFields}
updateHiddenFields={updateHiddenFields}
showInlineEdit={currentView === 'grid'}
inlineEdit={!!schema.inlineEdit}
setInlineEdit={(v) => onInlineEditChange?.(v)}
/>
)}

Expand Down
31 changes: 31 additions & 0 deletions packages/plugin-list/src/components/ViewSettingsPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ export interface ViewSettingsPopoverProps {
showHideFields?: boolean;
hiddenFields?: Set<string>;
updateHiddenFields?: (next: Set<string>) => void;

/** Record editing — toggle inline cell editing (persists `inlineEdit` on the view). */
showInlineEdit?: boolean;
inlineEdit?: boolean;
setInlineEdit?: (next: boolean) => void;
}

interface SectionProps {
Expand Down Expand Up @@ -131,6 +136,9 @@ export function ViewSettingsPopover(props: ViewSettingsPopoverProps) {
showHideFields,
hiddenFields,
updateHiddenFields,
showInlineEdit,
inlineEdit,
setInlineEdit,
} = props;

const [open, setOpen] = React.useState(false);
Expand All @@ -141,6 +149,7 @@ export function ViewSettingsPopover(props: ViewSettingsPopoverProps) {
!!rowColorConfig?.field,
density && density.mode !== 'compact',
(hiddenFields?.size ?? 0) > 0,
!!inlineEdit,
].filter(Boolean).length;

const DensityIcon =
Expand Down Expand Up @@ -266,6 +275,28 @@ export function ViewSettingsPopover(props: ViewSettingsPopoverProps) {
</Section>
)}

{showInlineEdit && setInlineEdit && (
<Section
title={t('list.recordEditingTitle', { defaultValue: 'Record editing' })}
defaultOpen={!!inlineEdit}
>
<label className="flex items-center gap-2 text-xs py-1 px-1 rounded hover:bg-muted cursor-pointer">
<input
type="checkbox"
checked={!!inlineEdit}
onChange={() => setInlineEdit(!inlineEdit)}
className="rounded border-input"
data-testid="view-settings-inline-edit"
/>
<span>
{t('list.inlineEditLabel', {
defaultValue: 'Edit records inline (click a cell to edit)',
})}
</span>
</label>
</Section>
)}

{showHideFields && hiddenFields && updateHiddenFields && (
<Section
title={t('list.hideFieldsTitle', { defaultValue: 'Hide fields' })}
Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/objectql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1936,6 +1936,12 @@ export interface ListViewSchema extends BaseSchema {
filter?: boolean;
rowHeight?: boolean;
addRecordForm?: boolean;
/**
* Allow end users to edit records inline (click a cell → type-aware editor,
* the same widgets the form uses). Default off: a list is read-only unless
* the author opts in. Read by InterfaceListPage → `inlineEdit`.
*/
editInline?: boolean;
buttons?: string[];
};

Expand Down
Loading