feat: add resizable kanban column width (#21828)
## What & why Lets users resize the columns of a Kanban (record board) view. Requested by a user; the design avoids the "ragged board" problem by making the width a **single shared value**. ## Behaviour - A drag handle appears on the right edge of every column header. - Because all columns read **one** width value, dragging any handle resizes **every** column together — they can never end up mismatched. - Width is clamped between **150px** and **400px** (default **200px**). - The width is **persisted per view** and restored on reload. ## Approach **Backend** — a new nullable `View.kanbanColumnWidth` field, threaded through the existing view-level setting pattern (the same one `kanbanAggregateOperation` / `shouldHideEmptyGroups` use), so it gets create/update/manifest/override support for free: - entity column + `ViewOverrides` + `@WasIntroducedInUpgrade` - `CreateViewInput` / `UpdateViewInput` (`Int`, `@Min(150)`/`@Max(400)`) + `ViewDTO` - flat-view editable properties, entity-properties config, compare-type, standard-view + manifest converters - a fast instance command adding the `core.view` column **Frontend** — the value hydrates into a view-scoped atom and drives a single CSS variable set on the board container, which both column headers and bodies read. Live dragging only writes that CSS variable (no per-move React re-render); the final width is committed to the atom and persisted via `updateView` on pointer-up. ## Nullability / defaults `kanbanColumnWidth` is nullable — `null` means "never resized" and the UI falls back to the 200px default, so existing rows need no backfill. ## Validation - `nx typecheck twenty-server` ✅ and `nx typecheck twenty-front` ✅ - `nx lint:diff-with-main twenty-server` ✅; frontend lint fixes applied (split constants to one-per-file, removed `useRef`-for-state in favour of `useState`). - Draft pending a final green CI run (the dev container reclaimed `node_modules` mid-session; re-running locally). ## Test plan - [ ] Drag a kanban column edge → all columns resize together, clamped 150–400px - [ ] Reload → width persists for that view; other views unaffected - [ ] A view that was never resized still renders at 200px https://claude.ai/code/session_016Qe6oDBkhVbrq2QkXBJ5nE --- _Generated by [Claude Code](https://claude.ai/code/session_016Qe6oDBkhVbrq2QkXBJ5nE)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21828?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -869,6 +869,7 @@ type View {
|
||||
kanbanAggregateOperationFieldMetadataId: UUID
|
||||
mainGroupByFieldMetadataId: UUID
|
||||
shouldHideEmptyGroups: Boolean!
|
||||
kanbanColumnWidth: Int
|
||||
calendarFieldMetadataId: UUID
|
||||
workspaceId: UUID!
|
||||
anyFieldFilterValue: String
|
||||
@@ -3575,6 +3576,7 @@ input CreateViewInput {
|
||||
position: Float = 0
|
||||
isCompact: Boolean = false
|
||||
shouldHideEmptyGroups: Boolean = false
|
||||
kanbanColumnWidth: Int
|
||||
openRecordIn: ViewOpenRecordIn = SIDE_PANEL
|
||||
kanbanAggregateOperation: AggregateOperations
|
||||
kanbanAggregateOperationFieldMetadataId: UUID
|
||||
@@ -3601,6 +3603,7 @@ input UpdateViewInput {
|
||||
visibility: ViewVisibility
|
||||
mainGroupByFieldMetadataId: UUID
|
||||
shouldHideEmptyGroups: Boolean
|
||||
kanbanColumnWidth: Int
|
||||
}
|
||||
|
||||
input UpsertViewWidgetInput {
|
||||
|
||||
@@ -602,6 +602,7 @@ export interface View {
|
||||
kanbanAggregateOperationFieldMetadataId?: Scalars['UUID']
|
||||
mainGroupByFieldMetadataId?: Scalars['UUID']
|
||||
shouldHideEmptyGroups: Scalars['Boolean']
|
||||
kanbanColumnWidth?: Scalars['Int']
|
||||
calendarFieldMetadataId?: Scalars['UUID']
|
||||
workspaceId: Scalars['UUID']
|
||||
anyFieldFilterValue?: Scalars['String']
|
||||
@@ -3631,6 +3632,7 @@ export interface ViewGenqlSelection{
|
||||
kanbanAggregateOperationFieldMetadataId?: boolean | number
|
||||
mainGroupByFieldMetadataId?: boolean | number
|
||||
shouldHideEmptyGroups?: boolean | number
|
||||
kanbanColumnWidth?: boolean | number
|
||||
calendarFieldMetadataId?: boolean | number
|
||||
workspaceId?: boolean | number
|
||||
anyFieldFilterValue?: boolean | number
|
||||
@@ -6202,9 +6204,9 @@ export interface DestroyViewFilterInput {
|
||||
/** The id of the view filter to destroy. */
|
||||
id: Scalars['UUID']}
|
||||
|
||||
export interface CreateViewInput {id?: (Scalars['UUID'] | null),name: Scalars['String'],objectMetadataId: Scalars['UUID'],type?: (ViewType | null),key?: (ViewKey | null),icon: Scalars['String'],position?: (Scalars['Float'] | null),isCompact?: (Scalars['Boolean'] | null),shouldHideEmptyGroups?: (Scalars['Boolean'] | null),openRecordIn?: (ViewOpenRecordIn | null),kanbanAggregateOperation?: (AggregateOperations | null),kanbanAggregateOperationFieldMetadataId?: (Scalars['UUID'] | null),anyFieldFilterValue?: (Scalars['String'] | null),calendarLayout?: (ViewCalendarLayout | null),calendarFieldMetadataId?: (Scalars['UUID'] | null),mainGroupByFieldMetadataId?: (Scalars['UUID'] | null),visibility?: (ViewVisibility | null)}
|
||||
export interface CreateViewInput {id?: (Scalars['UUID'] | null),name: Scalars['String'],objectMetadataId: Scalars['UUID'],type?: (ViewType | null),key?: (ViewKey | null),icon: Scalars['String'],position?: (Scalars['Float'] | null),isCompact?: (Scalars['Boolean'] | null),shouldHideEmptyGroups?: (Scalars['Boolean'] | null),kanbanColumnWidth?: (Scalars['Int'] | null),openRecordIn?: (ViewOpenRecordIn | null),kanbanAggregateOperation?: (AggregateOperations | null),kanbanAggregateOperationFieldMetadataId?: (Scalars['UUID'] | null),anyFieldFilterValue?: (Scalars['String'] | null),calendarLayout?: (ViewCalendarLayout | null),calendarFieldMetadataId?: (Scalars['UUID'] | null),mainGroupByFieldMetadataId?: (Scalars['UUID'] | null),visibility?: (ViewVisibility | null)}
|
||||
|
||||
export interface UpdateViewInput {id?: (Scalars['UUID'] | null),name?: (Scalars['String'] | null),type?: (ViewType | null),icon?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),isCompact?: (Scalars['Boolean'] | null),openRecordIn?: (ViewOpenRecordIn | null),kanbanAggregateOperation?: (AggregateOperations | null),kanbanAggregateOperationFieldMetadataId?: (Scalars['UUID'] | null),anyFieldFilterValue?: (Scalars['String'] | null),calendarLayout?: (ViewCalendarLayout | null),calendarFieldMetadataId?: (Scalars['UUID'] | null),visibility?: (ViewVisibility | null),mainGroupByFieldMetadataId?: (Scalars['UUID'] | null),shouldHideEmptyGroups?: (Scalars['Boolean'] | null)}
|
||||
export interface UpdateViewInput {id?: (Scalars['UUID'] | null),name?: (Scalars['String'] | null),type?: (ViewType | null),icon?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),isCompact?: (Scalars['Boolean'] | null),openRecordIn?: (ViewOpenRecordIn | null),kanbanAggregateOperation?: (AggregateOperations | null),kanbanAggregateOperationFieldMetadataId?: (Scalars['UUID'] | null),anyFieldFilterValue?: (Scalars['String'] | null),calendarLayout?: (ViewCalendarLayout | null),calendarFieldMetadataId?: (Scalars['UUID'] | null),visibility?: (ViewVisibility | null),mainGroupByFieldMetadataId?: (Scalars['UUID'] | null),shouldHideEmptyGroups?: (Scalars['Boolean'] | null),kanbanColumnWidth?: (Scalars['Int'] | null)}
|
||||
|
||||
export interface UpsertViewWidgetInput {
|
||||
/** The id of the view widget (page layout widget). */
|
||||
|
||||
@@ -1712,6 +1712,9 @@ export default {
|
||||
"shouldHideEmptyGroups": [
|
||||
6
|
||||
],
|
||||
"kanbanColumnWidth": [
|
||||
21
|
||||
],
|
||||
"calendarFieldMetadataId": [
|
||||
3
|
||||
],
|
||||
@@ -9352,6 +9355,9 @@ export default {
|
||||
"shouldHideEmptyGroups": [
|
||||
6
|
||||
],
|
||||
"kanbanColumnWidth": [
|
||||
21
|
||||
],
|
||||
"openRecordIn": [
|
||||
73
|
||||
],
|
||||
@@ -9426,6 +9432,9 @@ export default {
|
||||
"shouldHideEmptyGroups": [
|
||||
6
|
||||
],
|
||||
"kanbanColumnWidth": [
|
||||
21
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user