Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ The samples are located in `packages/samples/react` and demonstrate how to use t
- `.editorconfig` sets `indent_style = tab` and `max_line_length = 160` for code files. Markdown and YAML files use spaces.
- ESLint and Stylelint are run using `pnpm lint`. Pre‑commit hooks run `lint-staged` which formats and lints changed files. Lint rules should **not** be disabled via inline comments. Instead, describe the problem and work towards a clean solution.
- Lists and enumerations in code should be kept in alphanumeric order. This also applies to import specifiers and union type literals.
- Do not disable ESLint, Stylelint or TypeScript rules inline. Fix the code instead of turning such rules off.
- ESLint and Stylelint are run using `pnpm lint`. Pre‑commit hooks run `lint-staged` which formats and lints changed files.
- Lists and enumerations in code should be kept in alphabetical order (see `docs/tutorials/NEW_COMPONENT.md`).
- Commit messages follow the **Conventional Commits** specification.
- See also the [Contributing Guide](CONTRIBUTING.md) for more details on coding conventions and best practices.
- Spell "KoliBri" with this casing in all documentation and code. The only exception is the component named KolKolibri.
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/components/abbr/abbr.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ test.describe('kol-abbr', () => {
const tooltip = kolAbbr.locator('kol-tooltip-wc kol-span-wc');
await expect(tooltip).not.toBeVisible();
await kolAbbr.hover();
await page.waitForChanges();
await expect(tooltip).toBeVisible();
await expect(tooltip).toContainText('for example');
});
Expand Down
53 changes: 31 additions & 22 deletions packages/components/src/components/tooltip/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ export class KolTooltipWc implements TooltipAPI {
if (this.previousSibling && this.tooltipElement /* SSR instanceof HTMLElement */) {
showOverlay(this.tooltipElement);
tooltipOpened();
this.tooltipElement.classList.remove('hide');
this.tooltipElement.classList.add('show');
this.tooltipElement.style.setProperty('display', 'block');
getDocument().addEventListener('keyup', this.hideTooltipByEscape);
getDocument().addEventListener('keyup', this.hideTooltipByEscape, {
once: true,
});

const target = this.previousSibling;
const tooltipEl = this.tooltipElement;
Expand All @@ -57,8 +61,9 @@ export class KolTooltipWc implements TooltipAPI {
if (this.tooltipElement /* SSR instanceof HTMLElement */) {
hideOverlay(this.tooltipElement);
tooltipClosed();
this.tooltipElement.style.setProperty('display', 'none');
this.tooltipElement.style.setProperty('visibility', 'hidden');
this.tooltipElement.classList.remove('show');
this.tooltipElement.classList.add('hide');

if (this.cleanupAutoPositioning) {
this.cleanupAutoPositioning();
this.cleanupAutoPositioning = undefined;
Expand All @@ -73,35 +78,38 @@ export class KolTooltipWc implements TooltipAPI {
}
};

private handleMouseEnter() {
private handleMouseEnter = (): void => {
this.hasMouseIn = true;
this.showOrHideTooltip();
}
private handleMouseleave() {
this.hasMouseIn = false;
};

private handleMouseleave = (event: Event): void => {
this.hasMouseIn = this.tooltipElement?.contains((event as MouseEvent).relatedTarget as Node) ?? false;
this.showOrHideTooltip();
}
private handleFocusIn() {
};

private handleFocusIn = (): void => {
this.hasFocusIn = true;
this.showOrHideTooltip();
}
private handleFocusout() {
};

private handleFocusout = (): void => {
this.hasFocusIn = false;
this.showOrHideTooltip();
}
};

private addListeners = (el: Element): void => {
el.addEventListener('mouseenter', this.handleMouseEnter.bind(this));
el.addEventListener('mouseleave', this.handleMouseleave.bind(this));
el.addEventListener('focusin', this.handleFocusIn.bind(this));
el.addEventListener('focusout', this.handleFocusout.bind(this));
el.addEventListener('mouseenter', this.handleMouseEnter);
el.addEventListener('mouseleave', this.handleMouseleave);
el.addEventListener('focusin', this.handleFocusIn);
el.addEventListener('focusout', this.handleFocusout);
};

private removeListeners = (el: Element): void => {
el.removeEventListener('mouseenter', this.handleMouseEnter.bind(this));
el.removeEventListener('mouseleave', this.handleMouseleave.bind(this));
el.removeEventListener('focusin', this.handleFocusIn.bind(this));
el.removeEventListener('focusout', this.handleFocusout.bind(this));
el.removeEventListener('mouseenter', this.handleMouseEnter);
el.removeEventListener('mouseleave', this.handleMouseleave);
el.removeEventListener('focusin', this.handleFocusIn);
el.removeEventListener('focusout', this.handleFocusout);
};

private resyncListeners = (last?: Element | null, next?: Element | null, replacePreviousSibling = false): void => {
Expand Down Expand Up @@ -208,12 +216,13 @@ export class KolTooltipWc implements TooltipAPI {
}

private handleEventListeners(): void {
this.resyncListeners(this.previousSibling, this.host?.previousElementSibling, true);
const nextSibling = this.host?.previousElementSibling ?? null;
this.resyncListeners(this.previousSibling, nextSibling as Element, true);
this.resyncListeners(this.tooltipElement, this.tooltipElement);
}

public connectedCallback(): void {
this.previousSibling = this.host?.previousElementSibling;
this.previousSibling = this.host?.previousElementSibling ?? null;
}

public componentDidRender(): void {
Expand Down
11 changes: 11 additions & 0 deletions packages/components/src/components/tooltip/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ Um die Breite eines Tooltips zu konfigurieren, kann auf dem umgebenden Container
}
```

## Animation

Die Dauer der Ein- und Ausblendanimation kann \u00fcber CSS-Custom-Properties angepasst werden.
Die Dauer der Ein- und Ausblendanimation wird über `--kolibri-tooltip-animation-duration` gesteuert.

```css
body {
--kolibri-tooltip-animation-duration: 300ms;
}
```

## Links und Referenzen

- <kol-link _href="https://tollwerk.de/projekte/tipps-techniken-inklusiv-barrierefrei/titel-tooltips-toggletips" _target="_blank"></kol-link>
Expand Down
57 changes: 40 additions & 17 deletions packages/components/src/components/tooltip/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,34 @@
display: contents;
}

.kol-tooltip-wc .tooltip-floating {
display: none;
position: fixed;
/* Avoid layout interference - see https://floating-ui.com/docs/computePosition */
top: 0;
left: 0;
/* Can be used to specify the tooltip-width from the outside. Unset by default. */
width: var(--kol-tooltip-width);
max-width: 90vw;
max-height: 90vh;

visibility: hidden;
animation-duration: 0.25s;
animation-iteration-count: 1;
animation-name: fadeInOpacity;
animation-timing-function: ease-in;
.kol-tooltip-wc {
.tooltip-floating {
opacity: 0;
display: none;
position: fixed;

/* Avoid layout interference - see https://floating-ui.com/docs/computePosition */
top: 0;
left: 0;
/* Can be used to specify the tooltip-width from the outside. Unset by default. */
width: var(--kol-tooltip-width, unset);
max-width: 90vw;
max-height: 90vh;
animation-direction: normal;
/* Can be used to specify the animation duration from the outside. 250ms by default. */
animation-duration: var(--kolibri-tooltip-animation-duration, 250ms);
animation-fill-mode: forwards;
animation-iteration-count: 1;
animation-timing-function: ease-in;

&.hide {
animation-name: hideTooltip;
}

&.show {
animation-name: showTooltip;
}
}
}

/* Shared between content and arrow */
Expand All @@ -42,7 +54,18 @@
z-index: 1000;
}

@keyframes fadeInOpacity {
@keyframes hideTooltip {
0% {
opacity: 1;
}

100% {
opacity: 0;
display: none;
}
}

@keyframes showTooltip {
0% {
opacity: 0;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { FC } from 'react';
import React from 'react';

import { KolButton } from '@public-ui/react-v19';
import { SampleDescription } from '../components/SampleDescription';
import { useToasterService } from '../hooks/useToasterService';

export const CustomTooltipCssProperties: FC = () => {
const { dummyClickEventHandler } = useToasterService();

const dummyEventHandler = {
onClick: dummyClickEventHandler,
};

return (
<>
<SampleDescription>
<p>
This sample demonstrates how tooltip animation duration and width can be customized via
<code>--kolibri-tooltip-animation-duration</code> and <code>--kol-tooltip-width</code>.
</p>
</SampleDescription>

<div className="flex justify-center items-center gap-4">
<KolButton
_label="Custom duration"
_hideLabel
style={{ '--kolibri-tooltip-animation-duration': '2500ms' }}
_icons="codicon codicon-clock"
_on={dummyEventHandler}
></KolButton>
<KolButton
_label="Custom width"
_hideLabel
style={{ '--kol-tooltip-width': '400px' }}
_icons="codicon codicon-arrow-both"
_on={dummyEventHandler}
></KolButton>
</div>
</>
);
};
33 changes: 0 additions & 33 deletions packages/samples/react/src/scenarios/custom-tooltip-width.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions packages/samples/react/src/scenarios/routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Routes } from '../shares/types';
import { AppointmentForm } from './appointment-form/AppointmentForm';
import { ChangeTabindex } from './change-tabindex';
import { CustomTooltipWidth } from './custom-tooltip-width';
import { CustomTooltipCssProperties } from './custom-tooltip-css-properties';
import { DisabledInteractiveElements } from './disabled-interactive-elements';
import { FocusElements } from './focus-elements';
import { InputGroupWithError } from './input-group-with-error';
Expand All @@ -16,7 +16,7 @@ export const SCENARIO_ROUTES: Routes = {
scenarios: {
'appointment-form': AppointmentForm,
'change-tabindex': ChangeTabindex,
'custom-tooltip-width': CustomTooltipWidth,
'custom-tooltip-css-properties': CustomTooltipCssProperties,
'disabled-interactive-scenario': DisabledInteractiveElements,
'focus-elements': FocusElements,
'input-group-with-error': InputGroupWithError,
Expand Down
Loading