From 73ad39d0c366e7834f7e96896afaccd2a54e6e4f Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Mon, 22 Sep 2025 12:29:34 -0400 Subject: [PATCH] feat: dynamically hide tabs in app builder (#6653) * feat: dynamically hide tabs in app builder * claude code comments --- .../apps/components/layout/AppTabs.svelte | 218 +++++++++++------- .../editor/component/ComponentInner.svelte | 1 + .../apps/editor/component/components.ts | 11 +- .../settingsPanel/ComponentPanel.svelte | 2 + .../apps/editor/settingsPanel/GridTab.svelte | 23 ++ .../editor/settingsPanel/GridTabHidden.svelte | 62 +++++ 6 files changed, 233 insertions(+), 84 deletions(-) create mode 100644 frontend/src/lib/components/apps/editor/settingsPanel/GridTabHidden.svelte diff --git a/frontend/src/lib/components/apps/components/layout/AppTabs.svelte b/frontend/src/lib/components/apps/components/layout/AppTabs.svelte index 521c89ee6d..87396cac20 100644 --- a/frontend/src/lib/components/apps/components/layout/AppTabs.svelte +++ b/frontend/src/lib/components/apps/components/layout/AppTabs.svelte @@ -28,6 +28,7 @@ customCss?: ComponentCustomCSS<'tabscomponent'> | undefined render: boolean disabledTabs: RichConfiguration[] + hiddenTabs: RichConfiguration[] onTabChange?: string[] | undefined } @@ -39,6 +40,7 @@ customCss = undefined, render, disabledTabs, + hiddenTabs, onTabChange = undefined }: Props = $props() @@ -101,18 +103,28 @@ } }, left: () => { - const index = tabs.indexOf(selected) - if (index > 0) { - selected = tabs[index - 1] - return true + const currentIndex = tabs.indexOf(selected) + if (currentIndex > 0) { + // Find the next visible tab to the left + for (let i = currentIndex - 1; i >= 0; i--) { + if (!resolvedHiddenTabs[i]) { + selected = tabs[i] + return true + } + } } return false }, right: () => { - const index = tabs.indexOf(selected) - if (index < tabs.length - 1) { - selected = tabs[index + 1] - return true + const currentIndex = tabs.indexOf(selected) + if (currentIndex < tabs.length - 1) { + // Find the next visible tab to the right + for (let i = currentIndex + 1; i < tabs.length; i++) { + if (!resolvedHiddenTabs[i]) { + selected = tabs[i] + return true + } + } } return false }, @@ -144,6 +156,31 @@ }) let resolvedDisabledTabs: boolean[] = $state([]) + let resolvedHiddenTabs: boolean[] = $state([]) + + // Filter visible tabs based on hiddenTabs configuration + let visibleTabsIndices = $derived( + (tabs ?? []).map((_, index) => index).filter((index) => !resolvedHiddenTabs[index]) + ) + + // Auto-select first visible tab if current selection is hidden or invalid + $effect(() => { + if (visibleTabsIndices.length === 0) { + // Handle case where all tabs are hidden + selected = '' + selectedIndex = -1 + return + } + + const currentIndex = tabs?.indexOf(selected) ?? -1 + if (currentIndex === -1 || resolvedHiddenTabs[currentIndex]) { + const firstVisibleTab = tabs[visibleTabsIndices[0]] + if (selected !== firstVisibleTab) { + // Current selection is hidden or invalid, select first visible tab + selected = firstVisibleTab + } + } + }) @@ -169,6 +206,15 @@ /> {/each} +{#each hiddenTabs ?? [] as hideTab, index} + +{/each} + {#if everRender}
{#if !resolvedConfig.tabsKind || resolvedConfig.tabsKind == 'tabs' || (resolvedConfig.tabsKind == 'invisibleOnView' && $mode == 'dnd')} @@ -179,16 +225,18 @@ style={css?.tabRow?.style} > {#each tabs ?? [] as res, index} - - {res} - + {#if !resolvedHiddenTabs[index]} + + {res} + + {/if} {/each}
@@ -202,78 +250,82 @@ style={css?.tabRow?.style} > {#each tabs ?? [] as res, index} - + {#if !resolvedHiddenTabs[index]} + + {/if} {/each} {/if} {#if resolvedConfig.tabsKind == 'accordion'}
{#each tabs ?? [] as res, index} -
- -
- { - if (!$connectingInput.opened) { - $selectedComponent = [id] - handleTabSelection() - } - }} - /> + {#if !resolvedHiddenTabs[index]} +
+ +
+ { + if (!$connectingInput.opened) { + $selectedComponent = [id] + handleTabSelection() + } + }} + /> +
-
+ {/if} {/each}
{:else} diff --git a/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte b/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte index 9135de349e..52ed61403c 100644 --- a/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte +++ b/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte @@ -853,6 +853,7 @@ id={component.id} tabs={component.tabs} disabledTabs={component.disabledTabs} + hiddenTabs={component.hiddenTabs} onTabChange={component.onTabChange} customCss={component.customCss} {componentContainerHeight} diff --git a/frontend/src/lib/components/apps/editor/component/components.ts b/frontend/src/lib/components/apps/editor/component/components.ts index eb7b1ff9bc..d1235956fd 100644 --- a/frontend/src/lib/components/apps/editor/component/components.ts +++ b/frontend/src/lib/components/apps/editor/component/components.ts @@ -239,6 +239,7 @@ export type FileInputComponent = BaseComponent<'fileinputcomponent'> & { export type TabsComponent = BaseComponent<'tabscomponent'> & { tabs: string[] disabledTabs: RichConfiguration[] + hiddenTabs: RichConfiguration[] onTabChange?: string[] } @@ -3012,7 +3013,15 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm' }, componentInput: undefined, numberOfSubgrids: 2, - tabs: ['First tab', 'Second tab'] as string[] + tabs: ['First tab', 'Second tab'] as string[], + disabledTabs: [ + { type: 'static', value: false, fieldType: 'boolean' }, + { type: 'static', value: false, fieldType: 'boolean' } + ] as RichConfiguration[], + hiddenTabs: [ + { type: 'static', value: false, fieldType: 'boolean' }, + { type: 'static', value: false, fieldType: 'boolean' } + ] as RichConfiguration[] } }, steppercomponent: { diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte index 8992b2fa0a..39b9bc23f7 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte @@ -391,8 +391,10 @@ {:else if item.data.type === 'aggridcomponentee'} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/GridTab.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/GridTab.svelte index 2cbdc7d39e..955f1f8c6f 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/GridTab.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/GridTab.svelte @@ -13,11 +13,14 @@ import { generateRandomString } from '$lib/utils' import { GripVertical, Plus } from 'lucide-svelte' import GridTabDisabled from './GridTabDisabled.svelte' + import GridTabHidden from './GridTabHidden.svelte' interface Props { tabs?: string[] disabledTabs?: RichConfiguration[] + hiddenTabs?: RichConfiguration[] canDisableTabs?: boolean + canHideTabs?: boolean word?: string component: AppComponent } @@ -25,7 +28,9 @@ let { tabs = $bindable(undefined), disabledTabs = $bindable(undefined), + hiddenTabs = $bindable(undefined), canDisableTabs = false, + canHideTabs = false, word = 'Tab', component = $bindable() }: Props = $props() @@ -40,6 +45,12 @@ { type: 'static', value: false, fieldType: 'boolean' } ] } + if (hiddenTabs == undefined) { + hiddenTabs = [ + { type: 'static', value: false, fieldType: 'boolean' }, + { type: 'static', value: false, fieldType: 'boolean' } + ] + } }) let items = $state.raw( @@ -77,6 +88,7 @@ component.numberOfSubgrids = items.length disabledTabs = [...(disabledTabs ?? []), { type: 'static', value: false, fieldType: 'boolean' }] + hiddenTabs = [...(hiddenTabs ?? []), { type: 'static', value: false, fieldType: 'boolean' }] } function deleteSubgrid(index: number) { @@ -99,6 +111,9 @@ // Delete the item in the disabledTabs array disabledTabs = (disabledTabs ?? []).filter((_, i) => i !== index) + // Delete the item in the hiddenTabs array + hiddenTabs = (hiddenTabs ?? []).filter((_, i) => i !== index) + component.numberOfSubgrids = items.length // Update the originalIndex of the remaining items items.forEach((item, i) => { @@ -136,10 +151,13 @@ } const newDisabledTabs: RichConfiguration[] = [] + const newHiddenTabs: RichConfiguration[] = [] for (let i = 0; i < items.length; i++) { disabledTabs && newDisabledTabs.push(disabledTabs[items[i].originalIndex]) + hiddenTabs && newHiddenTabs.push(hiddenTabs[items[i].originalIndex]) } disabledTabs = newDisabledTabs + hiddenTabs = newHiddenTabs // update originalIndex items.forEach((item, i) => { @@ -203,6 +221,11 @@ {#if canDisableTabs && disabledTabs} {/if} + {#if canHideTabs && hiddenTabs} +
+ +
+ {/if}
{/each} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/GridTabHidden.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/GridTabHidden.svelte new file mode 100644 index 0000000000..de194d36e5 --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/GridTabHidden.svelte @@ -0,0 +1,62 @@ + + + { + if (hideable) { + field = { + type: 'evalv2', + expr: 'false', + fieldType: 'boolean', + connections: [] + } + } else { + field = { + type: 'static', + value: false, + fieldType: 'boolean' + } + } + }} +/> + +{#if hideable} +
+ +
+{/if} \ No newline at end of file