feat: expanded record as right-side slide-in panel

This commit is contained in:
Raju Udava
2026-05-13 12:39:44 +00:00
parent 8dcf29eb7a
commit 31066c00a0
4 changed files with 147 additions and 3 deletions

View File

@@ -0,0 +1,7 @@
<script setup lang="ts">
// CE stub — EE override at ee/components/smartsheet/grid/ExpandedFormPanel.vue
</script>
<template>
<NcSpanHidden />
</template>

View File

@@ -0,0 +1,7 @@
<script setup lang="ts">
// CE stub — EE override at ee/components/smartsheet/grid/ExpandedFormPanelActivity.vue
</script>
<template>
<NcSpanHidden />
</template>

View File

@@ -20,10 +20,17 @@ const { xWhere, eventBus, isExternalSource } = useSmartsheetStoreOrThrow()
const { t } = useI18n()
const { isMobileMode } = useGlobal()
const { isFeatureEnabled } = useBetaFeatureToggle()
const { blockExternalSourceRecordVisibility, showUpgradeToSeeMoreRecordsModal } = useEeConfig()
// --- Expanded form panel (right-side slide-in) ---
const expandedFormPanelStore = useProvideExpandedFormPanel()
const { isOpen: isExpandedFormPanelOpen, rowNavigator: expandedFormPanelRowNavigator } = expandedFormPanelStore
const bulkUpdateDlg = ref(false)
const routeQuery = computed(() => route.value.query as Record<string, string>)
@@ -191,6 +198,29 @@ const skipRowRemovalOnCancel = ref(false)
function expandForm(row: Row, state?: Record<string, any>, fromToolbar = false, path: Array<number> = []) {
const rowId = extractPkFromRow(row.row, meta.value?.columns as ColumnType[])
// EE desktop: open the right-side panel instead of modal
if (isEeUI && !isMobileMode.value && !isPublic.value && rowId) {
expandedFormPanelStore.openPanel(row, undefined, state)
// Update route for deep-linking
const routeParams = {
query: {
...routeQuery.value,
rowId,
path: ncIsEmptyArray(path) ? undefined : path.join('-'),
expand: undefined,
},
}
if (routeQuery.value.expand) {
router.replace(routeParams)
} else {
router.push(routeParams)
}
return
}
// Fallback: existing modal behavior (CE, mobile, public, new rows)
expandedFormRowState.value = state
if (rowId && !isPublic.value) {
expandedFormRow.value = undefined
@@ -204,11 +234,9 @@ function expandForm(row: Row, state?: Record<string, any>, fromToolbar = false,
colId: undefined,
cellMode: undefined,
path: ncIsEmptyArray(path) ? undefined : path.join('-'),
// Remove expand from query to avoid triggering the expanded form on closing the dialog
expand: undefined,
},
}
// if expand is true, replace the route to avoid adding a new history entry
if (routeQuery.value.expand) {
router.replace(routeParams)
} else {
@@ -233,6 +261,8 @@ defineExpose({
const expandedFormOnRowIdDlg = computed({
get() {
if (!routeQuery.value.rowId) return false
// When the side panel is open, don't trigger the modal
if (isExpandedFormPanelOpen.value) return false
// When ?colId points at a SmartText column the SmartText panel claims
// the URL — expanded record dialog stays closed.
const colId = routeQuery.value.colId
@@ -243,7 +273,11 @@ const expandedFormOnRowIdDlg = computed({
return true
},
set(val) {
if (!val)
if (!val) {
// Close panel if it's open
if (isExpandedFormPanelOpen.value) {
expandedFormPanelStore.closePanel()
}
router.push({
query: {
...routeQuery.value,
@@ -251,9 +285,20 @@ const expandedFormOnRowIdDlg = computed({
rowId: undefined,
},
})
}
},
})
// Close panel when route rowId is cleared (e.g. browser back)
watch(
() => routeQuery.value.rowId,
(newRowId) => {
if (!newRowId && isExpandedFormPanelOpen.value) {
expandedFormPanelStore.closePanel()
}
},
)
const addRowExpandOnClose = (row: Row) => {
if (!skipRowRemovalOnCancel.value) {
eventBus.emit(SmartsheetStoreEvents.CLEAR_NEW_ROW, row)
@@ -306,6 +351,31 @@ const updateViewWidth = () => {
const isInfiniteScrollingEnabled = computed(() => isFeatureEnabled(FEATURE_FLAG.INFINITE_SCROLLING))
// Wire row navigator for the expanded form panel
expandedFormPanelRowNavigator.value = {
getRow: (index: number) => {
if (isInfiniteScrollingEnabled.value) {
const row = cachedRows.value.get(index)
if (!row) return null
const rowId = extractPkFromRow(row.row, meta.value?.columns as ColumnType[])
if (!rowId) return null
return { rowId, row }
} else {
const row = pData.value[index]
if (!row) return null
const rowId = extractPkFromRow(row.row, meta.value?.columns as ColumnType[])
if (!rowId) return null
return { rowId, row }
}
},
totalRows: () => {
if (isInfiniteScrollingEnabled.value) {
return totalRows.value ?? 0
}
return pData.value.length
},
}
const isCanvasTableEnabled = computed(() => !ncIsPlaywright())
const isCanvasGroupByTableEnabled = computed(
@@ -622,6 +692,9 @@ watch([() => view.value?.id, () => meta.value?.columns], async () => {
</div>
<SmartsheetGridSmartTextPanel />
<!-- Right-side expanded form panel -->
<SmartsheetGridExpandedFormPanel />
</div>
</template>

View File

@@ -0,0 +1,57 @@
/**
* CE stub for expanded form panel composable.
* EE override at ee/composables/useExpandedFormPanel.ts provides the real implementation.
*/
const [useProvideExpandedFormPanel, useExpandedFormPanel] = useInjectionState(() => {
const isOpen = ref(false)
const activeRowId = ref<string | null>(null)
const activeRowIndex = ref<number | null>(null)
const isFullscreen = ref(false)
const panelWidth = ref(420)
const isLoading = ref(false)
const activityExpanded = ref(false)
const activeActivityTab = ref<'comments' | 'audits'>('comments')
const hasPrev = computed(() => false)
const hasNext = computed(() => false)
const activeDisplayValue = computed(() => null)
const rowNavigator = ref(null)
const openPanel = (_row: Row, _rowIndex?: number, _state?: Record<string, any>) => {}
const closePanel = () => {}
const setFullscreen = (_val: boolean) => {}
const navigatePrev = () => {}
const navigateNext = () => {}
const toggleActivity = (_tab?: 'comments' | 'audits') => {}
return {
isOpen,
activeRowId,
activeRowIndex,
isFullscreen,
panelWidth,
isLoading,
activityExpanded,
activeActivityTab,
hasPrev,
hasNext,
activeDisplayValue,
rowNavigator,
openPanel,
closePanel,
setFullscreen,
navigatePrev,
navigateNext,
toggleActivity,
}
}, 'expanded-form-panel-store')
export { useProvideExpandedFormPanel, useExpandedFormPanel }
export function useExpandedFormPanelOrThrow() {
const store = useExpandedFormPanel()
if (!store) throw new Error('useExpandedFormPanel must be used within a provider')
return store
}