Add tooltip per Tab header in app builder

This commit is contained in:
tristantr
2025-10-23 16:23:20 +02:00
parent eda978f7b0
commit 74cc8905f8
7 changed files with 114 additions and 27 deletions
@@ -29,6 +29,7 @@
render: boolean
disabledTabs: RichConfiguration[]
hiddenTabs: RichConfiguration[]
tooltipTabs: RichConfiguration[] | undefined
onTabChange?: string[] | undefined
}
@@ -41,6 +42,7 @@
render,
disabledTabs,
hiddenTabs,
tooltipTabs = undefined,
onTabChange = undefined
}: Props = $props()
@@ -157,7 +159,7 @@
let resolvedDisabledTabs: boolean[] = $state([])
let resolvedHiddenTabs: boolean[] = $state([])
let resolvedTooltipTabs: string[] = $state([])
// Filter visible tabs based on hiddenTabs configuration
let visibleTabsIndices = $derived(
(tabs ?? []).map((_, index) => index).filter((index) => !resolvedHiddenTabs[index])
@@ -210,6 +212,10 @@
<InputValue key="tabHidden {index}" {id} input={hideTab} bind:value={resolvedHiddenTabs[index]} />
{/each}
{#each tooltipTabs ?? [] as tooltipTab, index}
<InputValue key="tabTooltip {index}" {id} input={tooltipTab} bind:value={resolvedTooltipTabs[index]} />
{/each}
{#if everRender}
<div class={resolvedConfig.tabsKind == 'sidebar' ? 'flex gap-4 w-full h-full' : 'w-full'}>
{#if !resolvedConfig.tabsKind || resolvedConfig.tabsKind == 'tabs' || (resolvedConfig.tabsKind == 'invisibleOnView' && $mode == 'dnd')}
@@ -229,6 +235,9 @@
selectedStyle={css?.selectedTab?.style}
disabled={resolvedDisabledTabs[index]}
label={res}
tooltip={resolvedTooltipTabs[index] && String(resolvedTooltipTabs[index]).length > 0
? String(resolvedTooltipTabs[index])
: undefined}
/>
{/if}
{/each}
@@ -245,6 +254,11 @@
>
{#each tabs ?? [] as res, index}
{#if !resolvedHiddenTabs[index]}
<div
title={resolvedTooltipTabs[index] && String(resolvedTooltipTabs[index]).length > 0
? String(resolvedTooltipTabs[index])
: undefined}
>
<button
onpointerdown={stopPropagation(bubble('pointerdown'))}
onclick={() => !resolvedDisabledTabs[index] && (selected = res)}
@@ -269,7 +283,8 @@
: css?.allTabs?.style}
>
{res}
</button>
</button>
</div>
{/if}
{/each}
</div>
@@ -279,29 +294,35 @@
{#each tabs ?? [] as res, index}
{#if !resolvedHiddenTabs[index]}
<div class="border-b">
<button
onpointerdown={stopPropagation(bubble('pointerdown'))}
onclick={() => !resolvedDisabledTabs[index] && (selected = res)}
disabled={resolvedDisabledTabs[index]}
class={twMerge(
'w-full text-left bg-surface !truncate text-sm hover:text-primary px-1 py-2',
css?.allTabs?.class,
'wm-tabs-alltabs',
selected == res
? twMerge(
'bg-surface text-primary ',
css?.selectedTab?.class,
'wm-tabs-selectedTab'
)
: 'text-secondary',
resolvedDisabledTabs[index]
? 'opacity-50 cursor-not-allowed hover:text-secondary'
: ''
)}
<div
title={resolvedTooltipTabs[index] && String(resolvedTooltipTabs[index]).length > 0
? String(resolvedTooltipTabs[index])
: undefined}
>
<span class="mr-2 w-8 font-mono">{selected == res ? '-' : '+'}</span>
{res}
</button>
<button
onpointerdown={stopPropagation(bubble('pointerdown'))}
onclick={() => !resolvedDisabledTabs[index] && (selected = res)}
disabled={resolvedDisabledTabs[index]}
class={twMerge(
'w-full text-left bg-surface !truncate text-sm hover:text-primary px-1 py-2',
css?.allTabs?.class,
'wm-tabs-alltabs',
selected == res
? twMerge(
'bg-surface text-primary ',
css?.selectedTab?.class,
'wm-tabs-selectedTab'
)
: 'text-secondary',
resolvedDisabledTabs[index]
? 'opacity-50 cursor-not-allowed hover:text-secondary'
: ''
)}
>
<span class="mr-2 w-8 font-mono">{selected == res ? '-' : '+'}</span>
{res}
</button>
</div>
<div class={selected == res ? 'border-t' : ''}>
<SubGridEditor
{id}
@@ -858,6 +858,7 @@
tabs={component.tabs}
disabledTabs={component.disabledTabs}
hiddenTabs={component.hiddenTabs}
tooltipTabs={component.tooltipTabs}
onTabChange={component.onTabChange}
customCss={component.customCss}
{componentContainerHeight}
@@ -244,6 +244,7 @@ export type TabsComponent = BaseComponent<'tabscomponent'> & {
tabs: string[]
disabledTabs: RichConfiguration[]
hiddenTabs: RichConfiguration[]
tooltipTabs: RichConfiguration[]
onTabChange?: string[]
}
@@ -3124,6 +3125,10 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
hiddenTabs: [
{ type: 'static', value: false, fieldType: 'boolean' },
{ type: 'static', value: false, fieldType: 'boolean' }
] as RichConfiguration[],
tooltipTabs: [
{ type: 'static', value: '', fieldType: 'text' },
{ type: 'static', value: '', fieldType: 'text' },
] as RichConfiguration[]
}
},
@@ -393,9 +393,11 @@
bind:tabs={item.data.tabs}
bind:disabledTabs={item.data.disabledTabs}
bind:hiddenTabs={item.data.hiddenTabs}
bind:tooltipTabs={item.data.tooltipTabs}
bind:component={item.data}
canDisableTabs
canHideTabs
canTooltipTabs
/>
{:else if item.data.type === 'aggridcomponentee'}
<GridAgGridLicenseKey bind:license={item.data.license} />
@@ -14,13 +14,16 @@
import { GripVertical, Plus } from 'lucide-svelte'
import GridTabDisabled from './GridTabDisabled.svelte'
import GridTabHidden from './GridTabHidden.svelte'
import GridTabTooltip from './GridTabTooltip.svelte'
interface Props {
tabs?: string[]
disabledTabs?: RichConfiguration[]
hiddenTabs?: RichConfiguration[]
tooltipTabs?: RichConfiguration[]
canDisableTabs?: boolean
canHideTabs?: boolean
canTooltipTabs?: boolean
word?: string
component: AppComponent
}
@@ -29,8 +32,10 @@
tabs = $bindable(undefined),
disabledTabs = $bindable(undefined),
hiddenTabs = $bindable(undefined),
tooltipTabs = $bindable(undefined),
canDisableTabs = false,
canHideTabs = false,
canTooltipTabs = false,
word = 'Tab',
component = $bindable()
}: Props = $props()
@@ -51,6 +56,12 @@
{ type: 'static', value: false, fieldType: 'boolean' }
]
}
if (tooltipTabs == undefined) {
tooltipTabs = [
{ type: 'static', value: '', fieldType: 'text' },
{ type: 'static', value: '', fieldType: 'text' }
]
}
})
let items = $state.raw(
@@ -89,6 +100,7 @@
disabledTabs = [...(disabledTabs ?? []), { type: 'static', value: false, fieldType: 'boolean' }]
hiddenTabs = [...(hiddenTabs ?? []), { type: 'static', value: false, fieldType: 'boolean' }]
tooltipTabs = [...(tooltipTabs ?? []), { type: 'static', value: '', fieldType: 'text' }]
}
function deleteSubgrid(index: number) {
@@ -114,6 +126,8 @@
// Delete the item in the hiddenTabs array
hiddenTabs = (hiddenTabs ?? []).filter((_, i) => i !== index)
// Delete the item in the tooltipTabs array
tooltipTabs = (tooltipTabs ?? []).filter((_, i) => i !== index)
component.numberOfSubgrids = items.length
// Update the originalIndex of the remaining items
items.forEach((item, i) => {
@@ -152,13 +166,15 @@
const newDisabledTabs: RichConfiguration[] = []
const newHiddenTabs: RichConfiguration[] = []
const newTooltipTabs: RichConfiguration[] = []
for (let i = 0; i < items.length; i++) {
disabledTabs && newDisabledTabs.push(disabledTabs[items[i].originalIndex])
hiddenTabs && newHiddenTabs.push(hiddenTabs[items[i].originalIndex])
tooltipTabs && newTooltipTabs.push(tooltipTabs[items[i].originalIndex])
}
disabledTabs = newDisabledTabs
hiddenTabs = newHiddenTabs
tooltipTabs = newTooltipTabs
// update originalIndex
items.forEach((item, i) => {
item.originalIndex = i
@@ -226,6 +242,11 @@
<GridTabHidden {index} bind:field={hiddenTabs[index]} id={component.id} />
</div>
{/if}
{#if canTooltipTabs && tooltipTabs}
<div class="mt-2">
<GridTabTooltip {index} bind:field={tooltipTabs[index]} id={component.id} />
</div>
{/if}
</div>
{/each}
</section>
@@ -0,0 +1,30 @@
<script lang="ts">
import type { RichConfiguration } from '../../types'
import InputsSpecEditor from './InputsSpecEditor.svelte'
interface Props {
id: string
field: RichConfiguration
index: number
}
let { id, field = $bindable(), index }: Props = $props()
</script>
<InputsSpecEditor
key="tabTooltip {index}"
bind:componentInput={field}
{id}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={field?.['fieldType']}
subFieldType={field?.['subFieldType']}
format={field?.['format']}
selectOptions={field?.['selectOptions']}
tooltip={field?.['tooltip']}
fileUpload={field?.['fileUpload']}
placeholder={field?.['placeholder']}
customTitle="Tooltip"
displayType={false}
/>
@@ -21,6 +21,7 @@
exact?: boolean
otherValues?: string[]
disabled?: boolean
tooltip?: string
extra?: import('svelte').Snippet
}
@@ -39,6 +40,7 @@
exact = false,
otherValues = [],
disabled = false,
tooltip = undefined,
extra = undefined
}: Props = $props()
const { selected, update, hashNavigation } = getContext<TabsContext>('Tabs')
@@ -58,7 +60,11 @@
let isSelected = $derived(isSelectedFn($selected))
</script>
<button
<div
title={tooltip}
class="inline-block"
>
<button
use:triggerableByAI={{
id: aiId,
description: aiDescription,
@@ -109,4 +115,5 @@
{/if}
{@render extra?.()}
</div>
</button>
</button>
</div>