mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 2058: feat(crm): customer card copy-to-clipboard and carousel improvements
Customer Card Copy-to-Clipboard (#5508) - Click on card number copies it to clipboard using Angular CDK Clipboard - Shows success tooltip confirmation positioned on the right - Tooltip auto-dismisses after 3 seconds Card Stack Carousel Improvements (#5509) - Fix card centering by using afterNextRender instead of AfterViewInit - Add ResizeObserver to handle dynamic size changes - Disable transforms until natural position is measured (prevents initial jump) - Center single card in carousel view Tooltip Enhancements - Add success variant with green styling (isa-accent-green) - Add position input (left | right | top | bottom) - Add fade in/out CSS keyframes animations (150ms) - Respect prefers-reduced-motion for accessibility Related Tasks - Closes #5508 - Refs #5509
This commit is contained in:
committed by
Nino Righi
parent
7950994d66
commit
a5bb8b2895
@@ -36,15 +36,26 @@ export class TooltipComponent {
|
||||
/**
|
||||
* The visual variant of the tooltip.
|
||||
*/
|
||||
variant = signal<'default' | 'warning'>('default');
|
||||
variant = signal<'default' | 'warning' | 'success'>('default');
|
||||
|
||||
/**
|
||||
* Computed classes for the tooltip based on variant.
|
||||
* Whether the tooltip is in the leaving state (playing exit animation).
|
||||
*/
|
||||
leaving = signal(false);
|
||||
|
||||
/**
|
||||
* Computed classes for the tooltip based on variant and leaving state.
|
||||
*/
|
||||
tooltipClasses = computed(() => {
|
||||
const classes = ['ui-tooltip'];
|
||||
if (this.variant() === 'warning') {
|
||||
const v = this.variant();
|
||||
if (v === 'warning') {
|
||||
classes.push('ui-tooltip--warning');
|
||||
} else if (v === 'success') {
|
||||
classes.push('ui-tooltip--success');
|
||||
}
|
||||
if (this.leaving()) {
|
||||
classes.push('ui-tooltip--leaving');
|
||||
}
|
||||
return classes.join(' ');
|
||||
});
|
||||
|
||||
@@ -88,9 +88,12 @@ export class TooltipDirective implements OnDestroy {
|
||||
#tooltipInstance: TooltipComponent | null = null;
|
||||
#openTrigger: TooltipTrigger | null = null; // Tracks which trigger opened the tooltip
|
||||
#isOpen = signal(false); // Internal signal to track open state for CloseOnScrollDirective
|
||||
#isHiding = false; // Tracks if tooltip is currently animating out
|
||||
|
||||
// Distance between tooltip and anchor element
|
||||
readonly #offset = 8; // 0.5rem = 8px
|
||||
// Animation duration in ms (must match CSS)
|
||||
readonly #animationDuration = 150;
|
||||
|
||||
/** Optional title for the tooltip. */
|
||||
title = input<string>();
|
||||
@@ -111,7 +114,13 @@ export class TooltipDirective implements OnDestroy {
|
||||
* Visual variant of the tooltip.
|
||||
* Defaults to 'default'.
|
||||
*/
|
||||
variant = input<'default' | 'warning'>('default');
|
||||
variant = input<'default' | 'warning' | 'success'>('default');
|
||||
|
||||
/**
|
||||
* Preferred position of the tooltip relative to the host element.
|
||||
* Defaults to 'left'. Falls back to other positions if preferred doesn't fit.
|
||||
*/
|
||||
position = input<'left' | 'right' | 'top' | 'bottom'>('left');
|
||||
|
||||
constructor() {
|
||||
// Set up effects to update tooltip instance when inputs change
|
||||
@@ -155,45 +164,54 @@ export class TooltipDirective implements OnDestroy {
|
||||
|
||||
/**
|
||||
* Calculates the connected positions for the tooltip overlay.
|
||||
* The preferred position is to the left-center of the host element.
|
||||
* Fallback positions are right-center, bottom, and top.
|
||||
* Orders positions based on the preferred position input.
|
||||
* @returns An array of `ConnectedPosition` objects.
|
||||
*/
|
||||
#getPositions(): ConnectedPosition[] {
|
||||
return [
|
||||
{
|
||||
// Left-center position (default/preferred)
|
||||
originX: 'start',
|
||||
originY: 'center',
|
||||
overlayX: 'end',
|
||||
overlayY: 'center',
|
||||
offsetX: -this.#offset,
|
||||
},
|
||||
{
|
||||
// Right-center position
|
||||
originX: 'end',
|
||||
originY: 'center',
|
||||
overlayX: 'start',
|
||||
overlayY: 'center',
|
||||
offsetX: this.#offset,
|
||||
},
|
||||
{
|
||||
// Bottom position
|
||||
originX: 'start',
|
||||
originY: 'bottom',
|
||||
overlayX: 'start',
|
||||
overlayY: 'top',
|
||||
offsetY: this.#offset,
|
||||
},
|
||||
{
|
||||
// Top position
|
||||
originX: 'start',
|
||||
originY: 'top',
|
||||
overlayX: 'start',
|
||||
overlayY: 'bottom',
|
||||
offsetY: -this.#offset,
|
||||
},
|
||||
];
|
||||
const left: ConnectedPosition = {
|
||||
originX: 'start',
|
||||
originY: 'center',
|
||||
overlayX: 'end',
|
||||
overlayY: 'center',
|
||||
offsetX: -this.#offset,
|
||||
};
|
||||
|
||||
const right: ConnectedPosition = {
|
||||
originX: 'end',
|
||||
originY: 'center',
|
||||
overlayX: 'start',
|
||||
overlayY: 'center',
|
||||
offsetX: this.#offset,
|
||||
};
|
||||
|
||||
const bottom: ConnectedPosition = {
|
||||
originX: 'center',
|
||||
originY: 'bottom',
|
||||
overlayX: 'center',
|
||||
overlayY: 'top',
|
||||
offsetY: this.#offset,
|
||||
};
|
||||
|
||||
const top: ConnectedPosition = {
|
||||
originX: 'center',
|
||||
originY: 'top',
|
||||
overlayX: 'center',
|
||||
overlayY: 'bottom',
|
||||
offsetY: -this.#offset,
|
||||
};
|
||||
|
||||
// Order positions based on preferred position
|
||||
switch (this.position()) {
|
||||
case 'right':
|
||||
return [right, left, bottom, top];
|
||||
case 'top':
|
||||
return [top, bottom, left, right];
|
||||
case 'bottom':
|
||||
return [bottom, top, left, right];
|
||||
case 'left':
|
||||
default:
|
||||
return [left, right, bottom, top];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Shows the tooltip.
|
||||
@@ -259,9 +277,10 @@ export class TooltipDirective implements OnDestroy {
|
||||
|
||||
/**
|
||||
* Hides the tooltip only if the trigger matches the open trigger.
|
||||
* Plays fade-out animation before detaching.
|
||||
*/
|
||||
hide(trigger?: TooltipTrigger) {
|
||||
if (!this.#overlayRef) {
|
||||
if (!this.#overlayRef || this.#isHiding) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -284,14 +303,26 @@ export class TooltipDirective implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
this.#overlayRef.detach();
|
||||
this.#overlayRef.dispose();
|
||||
this.#overlayRef = null;
|
||||
this.#tooltipInstance = null;
|
||||
this.#openTrigger = null;
|
||||
// Start exit animation
|
||||
this.#isHiding = true;
|
||||
if (this.#tooltipInstance) {
|
||||
this.#tooltipInstance.leaving.set(true);
|
||||
}
|
||||
|
||||
// Update open state for CloseOnScrollDirective
|
||||
this.#isOpen.set(false);
|
||||
// Wait for animation to complete before detaching
|
||||
setTimeout(() => {
|
||||
if (this.#overlayRef) {
|
||||
this.#overlayRef.detach();
|
||||
this.#overlayRef.dispose();
|
||||
this.#overlayRef = null;
|
||||
}
|
||||
this.#tooltipInstance = null;
|
||||
this.#openTrigger = null;
|
||||
this.#isHiding = false;
|
||||
|
||||
// Update open state for CloseOnScrollDirective
|
||||
this.#isOpen.set(false);
|
||||
}, this.#animationDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,17 @@
|
||||
shadow-[0px_2px_8px_0px_rgba(223,0,27,0.15)];
|
||||
}
|
||||
|
||||
.ui-tooltip--success {
|
||||
@apply w-auto
|
||||
max-w-[12rem]
|
||||
p-3
|
||||
rounded-lg
|
||||
bg-green-50
|
||||
border
|
||||
border-isa-accent-green
|
||||
shadow-[0px_2px_8px_0px_rgba(0,128,0,0.15)];
|
||||
}
|
||||
|
||||
.ui-tooltip-title {
|
||||
@apply isa-text-body-2-bold text-isa-neutral-900;
|
||||
}
|
||||
@@ -39,6 +50,14 @@
|
||||
@apply text-isa-accent-red text-sm;
|
||||
}
|
||||
|
||||
.ui-tooltip--success .ui-tooltip-title {
|
||||
@apply text-isa-accent-green;
|
||||
}
|
||||
|
||||
.ui-tooltip--success .ui-tooltip-content {
|
||||
@apply text-isa-accent-green text-sm;
|
||||
}
|
||||
|
||||
/*
|
||||
Global styles for tooltip overlay container
|
||||
These styles will be injected into the global stylesheet
|
||||
@@ -48,10 +67,46 @@
|
||||
pointer-events: none; /* Allow clicks to pass through */
|
||||
}
|
||||
|
||||
/* Keyframes for tooltip animations */
|
||||
@keyframes tooltipFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tooltipFadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation for tooltip appearance */
|
||||
.ui-tooltip {
|
||||
opacity: 1;
|
||||
transition: opacity 150ms ease-in-out;
|
||||
animation: tooltipFadeIn 0.15s ease-out;
|
||||
}
|
||||
|
||||
.ui-tooltip--leaving {
|
||||
animation: tooltipFadeOut 0.15s ease-in forwards;
|
||||
}
|
||||
|
||||
/* Respect reduced motion preferences */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.ui-tooltip {
|
||||
animation: none;
|
||||
}
|
||||
.ui-tooltip--leaving {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-tooltip-icon {
|
||||
|
||||
Reference in New Issue
Block a user