Skip to content
New issue

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

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

Already on GitHub? Sign in 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

No branches or pull requests

1 participant