Compare commits

...

5 Commits

Author SHA1 Message Date
tristantr
67578250b3 Add tooltip toggle 2025-10-23 16:58:11 +02:00
tristantr
74cc8905f8 Add tooltip per Tab header in app builder 2025-10-23 16:23:20 +02:00
Tristan TR
eda978f7b0 Merge branch 'main' into tl/app-builder 2025-10-23 16:22:41 +02:00
Tristan TR
e039f57f85 Merge branch 'main' into tl/app-builder 2025-10-23 14:38:43 +02:00
tristantr
a74c680cf1 Add tooltip to Modal button and align mondalcomponent configuration names with other components 2025-10-23 14:35:37 +02:00
8 changed files with 173 additions and 38 deletions

View File

@@ -138,10 +138,16 @@
{#if everRender}
<div class="h-full w-full">
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
<div
class="inline-flex"
title={resolvedConfig.tooltip && String(resolvedConfig.tooltip).length > 0
? String(resolvedConfig.tooltip)
: undefined}
>
<Button
btnClasses={twMerge(css?.button?.class, 'wm-button', 'wm-modal-button')}
wrapperClasses={twMerge(
resolvedConfig?.buttonFillContainer ? 'w-full h-full' : '',
resolvedConfig?.fillContainer ? 'w-full h-full' : '',
css?.buttonContainer?.class,
'wm-button-container',
'wm-modal-button-container',
@@ -151,7 +157,7 @@
)}
style={css?.button?.style}
wrapperStyle={css?.buttonContainer?.style}
disabled={resolvedConfig.buttonDisabled}
disabled={resolvedConfig.disabled}
on:pointerdown={(e) => {
e?.stopPropagation()
}}
@@ -162,12 +168,13 @@
}
disposable?.openDrawer()
}}
extendedSize={resolvedConfig.buttonSize}
color={resolvedConfig.buttonColor}
extendedSize={resolvedConfig.size}
color={resolvedConfig.color}
variant="contained"
>
<div>{resolvedConfig.buttonLabel}</div>
</Button>
<div>{resolvedConfig.label}</div>
</Button>
</div>
</AlignWrapper>
</div>
<Portal target="#app-editor-top-level-drawer" name="app-modal">

View File

@@ -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}

View File

@@ -858,6 +858,7 @@
tabs={component.tabs}
disabledTabs={component.disabledTabs}
hiddenTabs={component.hiddenTabs}
tooltipTabs={component.tooltipTabs}
onTabChange={component.onTabChange}
customCss={component.customCss}
{componentContainerHeight}

View File

@@ -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[]
}
},
@@ -3609,12 +3614,12 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
value: false,
tooltip: 'Make button invisible when app is used outside of the edit mode'
},
buttonLabel: {
label: {
type: 'static',
fieldType: 'text',
value: 'Press me'
},
buttonColor: {
color: {
fieldType: 'select',
type: 'static',
@@ -3622,14 +3627,14 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
value: 'blue',
tooltip: 'These presets can be overwritten with custom styles.'
},
buttonSize: {
size: {
fieldType: 'select',
type: 'static',
selectOptions: selectOptions.buttonSizeOptions,
value: 'xs'
},
buttonFillContainer: {
fillContainer: {
fieldType: 'boolean',
type: 'static',
@@ -3637,10 +3642,16 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
tooltip:
'This will make the button fill the container width and height. Height and width can be overwritten with custom styles.'
},
buttonDisabled: {
disabled: {
fieldType: 'boolean',
type: 'static',
value: false
},
tooltip: {
type: 'static',
fieldType: 'text',
value: '',
tooltip: 'Tooltip text to show on hover'
}
},
componentInput: undefined,

View File

@@ -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} />

View File

@@ -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>

View File

@@ -0,0 +1,65 @@
<script lang="ts">
import Toggle from '$lib/components/Toggle.svelte'
import type { RichConfiguration } from '../../types'
import InputsSpecEditor from './InputsSpecEditor.svelte'
import { slide } from 'svelte/transition'
interface Props {
id: string
field: RichConfiguration
index: number
}
let { id, field = $bindable(), index }: Props = $props()
// Tooltip is active if it's not empty static value
let hasTooltip = $state(
field && !(field?.type === 'static' && (field?.value === '' || field?.value == null))
)
</script>
<Toggle
bind:checked={hasTooltip}
size="xs"
options={{
right: 'Tooltip'
}}
on:change={() => {
if (hasTooltip) {
field = {
type: 'evalv2',
expr: "''",
fieldType: 'text',
connections: []
}
} else {
field = {
type: 'static',
value: '',
fieldType: 'text'
}
}
}}
/>
{#if hasTooltip}
<div transition:slide|local>
<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}
/>
</div>
{/if}

View File

@@ -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>