Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Finish Tooltip to a beta stage #853

Open
9 tasks done
cwoolum opened this issue Jun 15, 2024 · 1 comment
Open
9 tasks done

Finish Tooltip to a beta stage #853

cwoolum opened this issue Jun 15, 2024 · 1 comment
Assignees

Comments

@cwoolum
Copy link
Contributor

cwoolum commented Jun 15, 2024

Tooltip Requirements

Anatomy:

<Tooltip.Root delayDuration={800} gutter={4} flip placement="top">
  <Tooltip.Trigger>
    <button>Hover or Focus me</button>
  </Tooltip.Trigger>
  <Tooltip.Panel aria-label="Tooltip content">
    <Tooltip.Arrow width={10} height={5} />
    Tooltip content here
  </Tooltip.Panel>
</Tooltip.Root>

Features:

Critical:

  • Opens on hover or focus
  • Closes on trigger activation or Escape key press
  • Customizable open/close delays
  • Optional arrow component
  • Always portaled content
  • Accessibility with ARIA roles and keyboard interactions
  • Flipping to avoid overflow
  • Automatic placement adjustment

Advanced:

Animations will need to wait until we can determine a reliable way to handle opening and closing. #913
- [ ] Custom animations

  • Theming support

Props:

Root:

  • open: boolean - The controlled open state of the tooltip.
  • bind:open: boolean - Reactive state of the tooltip.
  • onOpenChange$: function - Callback when the open state changes.
  • delayDuration: number - Delay before the tooltip opens.
  • gutter: number - Space between the trigger and the tooltip.
  • flip: boolean - Enable flipping to avoid overflow.
  • placement: "top" | "bottom" | "left" | "right" - The default position of the tooltip.

Trigger:

  • Data Attributes:
    • data-state: "closed" | "delayed-open" | "instant-open"

Panel:

  • aria-label: string - ARIA label for accessibility.
  • onEscapeKeyDown$: function - Function to handle Escape key press.
  • onPointerDownOutside$: function - Function to handle pointer down events outside the tooltip.
  • sticky: "partial" | "always" - Whether the tooltip should remain open when interacting with content inside.

Arrow:

  • width: number - The width of the arrow.
  • height: number - The height of the arrow.

Keyboard Interactions:

  • Down Arrow:
    • Opens the tooltip.
  • Up Arrow:
    • Moves focus to the previous focusable element.
  • Escape:
    • Closes the tooltip.
  • Enter:
    • Activates the trigger and toggles the tooltip.

ARIA Roles, States, and Properties for Tooltip

  1. Tooltip Element:

    • role="tooltip": Applied to the tooltip container element.
  2. Trigger Element:

    • aria-describedby: References the tooltip element's ID.
  3. Focus Management:

    • Tooltip does not receive focus. Focus remains on the owning element.
      Escape: Closes the tooltip.
  4. Interaction:

    • Tooltip appears on hover or focus.
    • Closes automatically on mouse out or when focus is lost.
    • Stays open when hovered.

Example Usage:

import { component$ } from '@builder.io/qwik';
import { Tooltip } from '@qwik-ui/headless';

export const App = component$(() => {
  return (
    <Tooltip.Root defaultOpen={false} delayDuration={800} gutter={4} flip placement="top" autoPlacement>
      <Tooltip.Trigger>
        <button>Hover or Focus me</button>
      </Tooltip.Trigger>
      <Tooltip.Panel aria-label="Tooltip content">
        <Tooltip.Arrow width={10} height={5} />
        Tooltip content here
      </Tooltip.Panel>
    </Tooltip.Root>
  );
});

This comprehensive guide provides detailed information on the anatomy, features, props, and usage of the Tooltip component in Qwik, ensuring a consistent and accessible implementation.

Existing Tooltip Libraries Comparison

To determine a basic feature set for the tooltip component, I compared the tooltop components from Melt, Kobalte, and Radix. I'll start with the common features they all share. You can read about them more individually below.

Common Features


Trigger Mechanism

  • Focus or Hover: All frameworks open the tooltip when the trigger element is focused or hovered.

  • Melt UI: Opens on focus or hover.

  • Kobalte UI: Opens on keyboard focus or mouse hover.

  • Radix UI: Opens on focus or hover.

Closing Mechanism

  • Activation or Escape Key: All frameworks close the tooltip when the trigger element is activated or when the Escape key is pressed.

  • Melt UI: Closes when the trigger is activated or Escape is pressed.

  • Kobalte UI: Closes when the trigger is activated or Escape is pressed.

  • Radix UI: Closes when the trigger is activated or Escape is pressed.

Customizable Delays

  • Open and Close Delays: All frameworks support customizable delays for opening and closing the tooltip.

  • Melt UI: openDelay and closeDelay props.

  • Kobalte UI: openDelay and closeDelay props.

  • Radix UI: delayDuration and skipDelayDuration props (via Provider) and delayDuration prop on Root.

Optional Arrow

  • Tooltip Arrow: All frameworks provide an optional arrow component that points to the trigger element.

  • Melt UI: Optional arrow component.

  • Kobalte UI: Optional Tooltip.Arrow component.

  • Radix UI: Optional Tooltip.Arrow component.

Portaling

  • Portal Support: All frameworks support rendering the tooltip content into a portal, typically the body element, to manage overlay issues.

  • Melt UI: portal prop.

  • Kobalte UI: Tooltip.Portal component.

  • Radix UI: Tooltip.Portal component.

Positioning

  • Melt UI: Provides positioning via the positioning prop.

  • Kobalte UI: Provides positioning via the placement prop and gutter props. Also supports flip.

  • Radix UI: Provides positioning using side and sideOffset props.

Accessibility

  • ARIA and Keyboard Support: All frameworks follow the ARIA design pattern for tooltips and provide proper keyboard interactions for accessibility.

  • Melt UI: Adheres to WAI-ARIA tooltip role design pattern.

  • Kobalte UI: Exposed to assistive technology via ARIA and supports aria-describedby.

  • Radix UI: Follows ARIA design pattern and provides keyboard interaction support.

Summary of Commonalities

  1. Trigger Mechanism: Opens on focus or hover.

  2. Closing Mechanism: Closes on activation or Escape key press.

  3. Customizable Delays: Support for custom open and close delays.

  4. Optional Arrow: Ability to include an arrow pointing to the trigger.

  5. Portaling: Ability to portal the tooltip content to manage overlays.

  6. Positioning: Ability to define the position of the tooltip.

  7. Accessibility: Compliance with ARIA design patterns and keyboard interaction support.

Individual tooltip features

Melt UI

Sample code

  import { createTooltip, melt } from '@melt-ui/svelte';
  import { fade } from 'svelte/transition';
  import { Plus } from '$icons/index.js';

  const {
    elements: { trigger, content, arrow },
    states: { open },
  } = createTooltip({
    positioning: {
      placement: 'top',
    },
    openDelay: 0,
    closeDelay: 0,
    closeOnPointerDown: false,
    forceVisible: true,
  });
</script>

<button type="button" class="trigger" {...$trigger} use:trigger aria-label="Add">
  <Plus class="size-4" aria-label="plus" />
</button>

{#if $open}
  <div
    {...$content} use:content
    transition:fade={{ duration: 100 }}
    class=" z-10 rounded-lg bg-white shadow"
  >
    <div {...$arrow} use:arrow />
    <p class="px-4 py-1 text-magnum-700">Add item to library</p>
  </div>
{/if}

Features:

  • Opens when the trigger is focused or hovered.

  • Closes when the trigger is activated or with escape.

  • Custom delay for opening and closing.

  • Supports custom positioning.

Anatomy:

  • Trigger: The element that triggers the tooltip popover.

  • Content: The tooltip's content container.

  • Arrow: An optional arrow component.

API Reference:

  • createTooltip: The builder function used to create the tooltip component.

  • Props:

  • positioning: Determines how the floating element is positioned relative to the trigger.

  • arrowSize: The size of the arrow in pixels.

  • escapeBehavior: Defines how the tooltip reacts when the Escape key is pressed.

  • forceVisible: Whether to force the tooltip to always be visible.

  • portal: The element or selector to render the tooltip into.

  • closeOnPointerDown: Whether the tooltip closes when the pointer is down.

  • openDelay: The delay in milliseconds before the tooltip opens after a pointer over event.

  • closeDelay: The delay in milliseconds before the tooltip closes after a pointer leave event.

  • disableHoverableContent: Prevents the tooltip content element from remaining open when hovered.

  • group: Controls the behavior of other tooltips when this one is opened.

  • defaultOpen: Whether the tooltip is open by default or not.

  • open: A writable store that controls whether the tooltip is open.

  • onOpenChange: A callback called when the value of the open store should be changed.

  • ids: Override internally generated ids for the elements.

Elements:

  • Trigger, Content, Arrow: Builder stores used to create respective elements.

States:

  • open: A writable store indicating whether the tooltip is open or not.

Options:

  • Options related to positioning, arrow size, escape behavior, visibility, portal rendering, delay, etc.

Accessibility:

  • Adheres to the WAI-ARIA tooltip role design pattern.

  • Tooltips are only activated on hover or focus, not on press.

Kobalte UI

Sample code

import { Tooltip } from "@kobalte/core/tooltip";
import "./style.css";

function App() {
  return (
    <Tooltip>
      <Tooltip.Trigger class="tooltip__trigger">Trigger</Tooltip.Trigger>
      <Tooltip.Portal>
        <Tooltip.Content class="tooltip__content">
          <Tooltip.Arrow />
          <p>Tooltip content</p>
        </Tooltip.Content>
      </Tooltip.Portal>
    </Tooltip>
  );
}

Features:

  • Exposed as a tooltip to assistive technology via ARIA.

  • Opens when the trigger is focused or hovered.

  • Closes when the trigger is activated or when pressing escape.

  • Only one tooltip shows at a time.

  • Labeling support for screen readers via aria-describedby.

  • Custom show and hide delay support.

  • Matches native tooltip behavior with delay on hover of first tooltip and no delay on subsequent tooltips.

Anatomy:

  • Tooltip: The root container for a tooltip.

  • Tooltip.Trigger: The button that toggles the tooltip.

  • Tooltip.Portal: Portals its children into the body when the tooltip is open.

  • Tooltip.Content: Contains the content to be rendered when the tooltip is open.

  • Tooltip.Arrow: An optional arrow element to render alongside the tooltip.

API Reference:

  • Tooltip:

  • Props:

  • open: The controlled open state of the tooltip.

  • defaultOpen: The default open state when initially rendered.

  • onOpenChange: Event handler called when the open state of the tooltip changes.

  • triggerOnFocusOnly: Whether to open the tooltip only when the trigger is focused.

  • openDelay: The duration from when the mouse enters the trigger until the tooltip opens.

  • closeDelay: The duration from when the mouse leaves the trigger or content until the tooltip closes.

  • ignoreSafeArea: Whether to close the tooltip even if the user cursor is inside the safe area between the trigger and tooltip.

  • id: A unique identifier for the component.

  • forceMount: Used to force mounting the tooltip (portal and content) when more control is needed.

  • Customizing Tooltip.Content Placement:

  • Various props to customize the placement of the Tooltip.Content like placement, gutter, shift, flip, slide, overlap, sameWidth, fitViewport, hideWhenDetached, detachedPadding, arrowPadding, overflowPadding.

  • Tooltip.Trigger, Tooltip.Content, Tooltip.Arrow:

  • Data attributes and props specific to each component.

Accessibility:

  • Keyboard Interactions:

  • Tab: Opens/closes the tooltip without delay.

  • Space: When open, closes the tooltip without delay.

  • Enter: When open, closes the tooltip without delay.

  • Esc: When open, closes the tooltip without delay.

Radix

Sample code

import React from 'react';
import * as Tooltip from '@radix-ui/react-tooltip';
import { PlusIcon } from '@radix-ui/react-icons';
import './styles.css';

const TooltipDemo = () => {
  return (
    <Tooltip.Provider>
      <Tooltip.Root>
        <Tooltip.Trigger asChild>
          <button className="IconButton">
            <PlusIcon />
          </button>
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content className="TooltipContent" sideOffset={5}>
            Add to library
            <Tooltip.Arrow className="TooltipArrow" />
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  );
};

Features:

  • Provider to control display delay globally.

  • Opens when the trigger is focused or hovered.

  • Closes when the trigger is activated or when pressing escape.

  • Supports custom timings.

Anatomy:

  • Provider: Wraps your app to provide global functionality to your tooltips.

  • Root: Contains all the parts of a tooltip.

  • Trigger: The button that toggles the tooltip.

  • Portal: Portals the content part into the body.

  • Content: The component that pops out when the tooltip is open.

  • Arrow: An optional arrow element to render alongside the tooltip.

API Reference:

  • Provider:

  • Props:

  • delayDuration: Global delay duration for tooltips.

  • skipDelayDuration: Duration to skip delay for tooltips.

  • disableHoverableContent: Disable hoverable content.

  • Root:

  • Props:

  • defaultOpen: Default open state.

  • open: Controlled open state.

  • onOpenChange: Event handler for open state changes.

  • delayDuration: Delay duration for tooltip.

  • disableHoverableContent: Disable hoverable content.

  • Trigger, Portal, Content, Arrow:

  • Various props and data attributes specific to each component.

Examples:

  • Configure globally: Use the Provider to control delayDuration and skipDelayDuration globally.

  • Show instantly: Use the delayDuration prop to control the time it takes for the tooltip to open.

  • Constrain the content size: Constrain the width and height of the content to match the trigger and viewport.

  • Origin-aware animations: Use CSS custom properties to animate the content based on its origin.

  • Collision-aware animations: Use data attributes to create collision and direction-aware animations.

Accessibility:

  • Keyboard Interactions:

  • Tab: Opens/closes the tooltip without delay.

  • Space: If open, closes the tooltip without delay.

  • Enter: If open, closes the tooltip without delay.

  • Esc: If open, closes the tooltip without delay.

@cwoolum cwoolum self-assigned this Jun 15, 2024
@cwoolum cwoolum converted this from a draft issue Jun 15, 2024
@cwoolum
Copy link
Contributor Author

cwoolum commented Jun 26, 2024

Implementation has started.

@cwoolum cwoolum moved this from In Progress to In Review in Qwik UI Development Aug 7, 2024
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
Archived in project
Development

No branches or pull requests

1 participant