From 889e6702ce520386da7a6e1596492909b0a591fb Mon Sep 17 00:00:00 2001
From: galangel
Date: Wed, 14 Jan 2026 14:40:29 +0200
Subject: [PATCH 1/2] Initial
Signed-off-by: galangel
---
.github/FUNDING.yml | 15 +
.storybook/preview.ts | 10 +-
README.md | 289 ++---
package-lock.json | 13 +-
package.json | 5 +-
src/KeyboardShortcuts.mdx | 288 ++---
src/TipAdvisor.mdx | 1087 +++++++++++++++++
src/Welcome.mdx | 120 +-
src/components/TipAdvisor/TipAdvisor.tsx | 197 +++
src/components/TipAdvisor/index.ts | 1 +
.../TipAdvisor/useTipAdvisorState.ts | 251 ++++
.../utils/__tests__/collectItems.test.ts | 212 ++++
.../utils/__tests__/fuzzySearch.test.ts | 131 ++
.../__tests__/keyboardNavigation.test.ts | 79 ++
.../utils/__tests__/tooltipPayload.test.ts | 106 ++
.../TipAdvisor/utils/collectItems.ts | 70 ++
.../TipAdvisor/utils/fuzzySearch.ts | 62 +
src/components/TipAdvisor/utils/index.ts | 8 +
.../TipAdvisor/utils/keyboardNavigation.ts | 39 +
.../TipAdvisor/utils/tooltipPayload.ts | 39 +
src/components/Tooltip/Tooltip.tsx | 22 +-
.../Tooltip/__tests__/Tooltip.test.tsx | 15 +-
src/components/index.ts | 1 +
src/constants/index.ts | 7 +-
src/hooks/index.ts | 1 +
src/hooks/useTipAdvisor.ts | 52 +
src/hooks/useTooltipAPI.ts | 4 +-
src/index.ts | 11 +-
.../Tooltip/KeyboardShortcuts.stories.tsx | 76 +-
src/stories/Tooltip/TheTipAdvisor.stories.tsx | 217 ++++
src/stories/Tooltip/TheTooltip.stories.tsx | 23 +-
src/stories/Tooltip/TipAdvisor.stories.tsx | 901 ++++++++++++++
src/styles/index.css | 3 +
src/styles/tip-advisor.css | 552 +++++++++
src/types/index.ts | 22 +-
src/types/tipAdvisor.ts | 103 ++
src/utils/__tests__/getTipProps.test.ts | 24 +-
.../__tests__/parseDataAttributes.test.ts | 54 +-
src/utils/getTipProps.ts | 13 +-
src/utils/parseDataAttributes.ts | 34 +-
40 files changed, 4606 insertions(+), 551 deletions(-)
create mode 100644 .github/FUNDING.yml
create mode 100644 src/TipAdvisor.mdx
create mode 100644 src/components/TipAdvisor/TipAdvisor.tsx
create mode 100644 src/components/TipAdvisor/index.ts
create mode 100644 src/components/TipAdvisor/useTipAdvisorState.ts
create mode 100644 src/components/TipAdvisor/utils/__tests__/collectItems.test.ts
create mode 100644 src/components/TipAdvisor/utils/__tests__/fuzzySearch.test.ts
create mode 100644 src/components/TipAdvisor/utils/__tests__/keyboardNavigation.test.ts
create mode 100644 src/components/TipAdvisor/utils/__tests__/tooltipPayload.test.ts
create mode 100644 src/components/TipAdvisor/utils/collectItems.ts
create mode 100644 src/components/TipAdvisor/utils/fuzzySearch.ts
create mode 100644 src/components/TipAdvisor/utils/index.ts
create mode 100644 src/components/TipAdvisor/utils/keyboardNavigation.ts
create mode 100644 src/components/TipAdvisor/utils/tooltipPayload.ts
create mode 100644 src/hooks/useTipAdvisor.ts
create mode 100644 src/stories/Tooltip/TheTipAdvisor.stories.tsx
create mode 100644 src/stories/Tooltip/TipAdvisor.stories.tsx
create mode 100644 src/styles/tip-advisor.css
create mode 100644 src/types/tipAdvisor.ts
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..d3bd82b
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,15 @@
+# These are supported funding model platforms
+
+github: galangel
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+polar: # Replace with a single Polar username
+buy_me_a_coffee: galangel
+thanks_dev: # Replace with a single thanks.dev username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
index 596ed98..0441788 100644
--- a/.storybook/preview.ts
+++ b/.storybook/preview.ts
@@ -21,15 +21,7 @@ const preview: Preview = {
options: {
// Sort stories with Welcome first
storySort: {
- order: [
- 'Welcome',
- 'Tooltip',
- 'Flows',
- 'Keyboard Shortcuts',
- 'Transitions',
- 'Examples',
- '*',
- ],
+ order: ['Welcome', 'The Tooltip', 'The Tourtip', 'The TipAdvisor', 'Tooltip', '*'],
},
},
},
diff --git a/README.md b/README.md
index a44d55c..8a59a30 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,23 @@
# React Tip Magic β¨
-A sophisticated, elegant, and performant tooltip library for React with an intelligent floating helper system.
+A thoughtfully crafted tooltip library for Reactβsimple to use, delightful to experience.



-## Features
+## Why React Tip Magic?
-- π― **Zero-config tooltips** - Just add `data-tip="Hello"` to any element
-- π **High performance** - Single global instance, minimal re-renders
-- π¨ **Smooth transitions** - Tooltips gracefully move between elements
-- π€ **Intelligent Helper** - Floating assistant with multiple states and actions
-- π± **Accessible** - Full keyboard navigation and screen reader support
-- π **Customizable** - Extensive theming and configuration options
-- π¦ **Lightweight** - Tree-shakeable, minimal dependencies
+Tooltips seem simple, but getting them right takes care. They should appear when needed, stay out of the way when not, and feel natural as users navigate your interface.
-## Quick Start
+React Tip Magic handles the details so you don't have toβpositioning, transitions, accessibility, keyboard supportβall with a clean, declarative API.
-### Installation
+## Quick Start
```bash
npm install @galangel/react-tip-magic
```
-### Basic Setup
-
```tsx
import { TipMagicProvider } from '@galangel/react-tip-magic';
import '@galangel/react-tip-magic/styles.css';
@@ -39,185 +31,200 @@ function App() {
}
```
-### Simple Tooltip
+That's it. Now add `data-tip` to any element:
```tsx
-
+
```
-### Tooltip with Keyboard Shortcut
+---
-```tsx
-
-```
+## Tooltips
+
+The core of React Tip Magic. One tooltip instance that gracefully moves between elements, providing a smooth, cohesive experience.
-### Advanced Options
+### Basic Usage
```tsx
-
- Hover me
-
+
+
```
-### Transition Behavior
+### With Keyboard Shortcuts
-Control how tooltips transition when moving between elements:
+Display shortcuts alongside your tooltips using the `data-tip-shortcut` attribute:
```tsx
-{/* Smooth move transition (default) */}
-
-
-
-{/* Jump transition (fade out/in) */}
-
+
+
+
```
-### Tooltip Groups
-
-Use `data-tip-group` to control move transitions between grouped elements. Tooltips will only smoothly move between elements in the **same group**:
+### Positioning & Behavior
```tsx
-{/* Group A - tooltips move smoothly within this group */}
-
-
-
-{/* Group B - tooltips move smoothly within this group */}
-
-
+{
+ /* Position control */
+}
+;
-{/* Moving from Group A to Group B will jump, not move */}
+{
+ /* Smooth transitions between grouped elements */
+}
+;
+
+{
+ /* Interactive tooltips that stay visible on hover */
+}
+
+ More info
+;
```
-**Group transition rules:**
+---
-- **Same group** β Smooth move transition
-- **Different groups** β Jump transition (tooltip appears at new position)
-- **Grouped to ungrouped** (or vice versa) β Smooth move transition
+## Guided Tours
-## Helper System
-
-The Helper is an optional floating element that provides contextual information and actions.
-
-### Onboarding Flow Example
+Walk users through your interface with step-by-step tours. Helpful for onboarding, feature introductions, or contextual guidance.
```tsx
-import { useTipMagic } from '@galangel/react-tip-magic';
+import { useTour } from '@galangel/react-tip-magic';
-function OnboardingFlow() {
- const { helper } = useTipMagic();
-
- useEffect(() => {
- helper.startFlow([
- {
- targetId: 'welcome-1',
- message: 'Welcome! This is your dashboard.',
- actions: [{ label: 'Next', action: 'next' }],
- },
- {
- targetId: 'welcome-2',
- message: 'Click here to create your first project.',
- actions: [{ label: 'Got it!', action: 'complete' }],
- },
- ]);
- }, []);
+function App() {
+ const tour = useTour({
+ steps: [
+ { target: 'dashboard', title: 'Welcome', message: 'This is your dashboard.' },
+ { target: 'create-btn', title: 'Create', message: 'Click here to get started.' },
+ ],
+ });
return (
-
-
+
+
);
}
```
-### Helper States
+Tours include navigation controls, progress indicators, keyboard support, and backdrop highlightingβall configurable to fit your needs.
+
+---
+
+## Tip Advisor
+
+A keyboard shortcut discovery menu. Press a key (F1 by default) to reveal all available shortcuts in your interface, with fuzzy search to quickly find what you need.
```tsx
-// Show thinking state
-helper.setState('thinking');
-
-// Show working state with message
-helper.show({
- state: 'working',
- message: 'Processing your request...',
-});
-
-// Show call to action
-helper.show({
- state: 'cta',
- message: 'Ready to continue?',
- actions: [
- { label: 'Yes', onClick: () => {} },
- { label: 'No', onClick: () => {} },
- ],
-});
+import { TipAdvisor } from '@galangel/react-tip-magic';
+
+function App() {
+ return (
+
+
+
+
+
+
+
+ {/* Press F1 to open the advisor */}
+
+
+ );
+}
```
+Features:
+
+- Fuzzy search with highlighted matches
+- Keyboard navigation (arrow keys + Enter)
+- Hover to preview tooltip locations
+- Click to trigger the associated element
+
+---
+
+## Data Attributes
+
+| Attribute | Description | Example |
+| ---------------------- | ----------------------------------- | ----------------------------- |
+| `data-tip` | Tooltip content | `data-tip="Hello"` |
+| `data-tip-shortcut` | Keyboard shortcut badge | `data-tip-shortcut="βS"` |
+| `data-tip-id` | Element identifier for tours | `data-tip-id="welcome"` |
+| `data-tip-placement` | Position (top, bottom, left, right) | `data-tip-placement="bottom"` |
+| `data-tip-delay` | Show delay in ms | `data-tip-delay="500"` |
+| `data-tip-hide-delay` | Hide delay in ms | `data-tip-hide-delay="100"` |
+| `data-tip-disabled` | Disable tooltip | `data-tip-disabled` |
+| `data-tip-html` | Parse content as HTML | `data-tip-html` |
+| `data-tip-interactive` | Keep tooltip on hover | `data-tip-interactive` |
+| `data-tip-move` | Smooth move transition | `data-tip-move` |
+| `data-tip-group` | Group for transitions | `data-tip-group="nav"` |
+| `data-tip-no-arrow` | Hide tooltip arrow | `data-tip-no-arrow` |
+
+---
+
## Programmatic API
-Use the `useTipMagic` hook for full programmatic control:
+For more control, use the `useTipMagic` hook:
```tsx
import { useTipMagic } from '@galangel/react-tip-magic';
function MyComponent() {
- const { tooltip, helper, config } = useTipMagic();
-
- // Show tooltip programmatically
- tooltip.show('#my-element', 'Custom content');
+ const { tooltip } = useTipMagic();
- // Hide tooltip
- tooltip.hide();
+ const handleClick = () => {
+ tooltip.show('#my-element', 'Dynamic content');
+ };
- // Update content dynamically
- tooltip.updateContent('New content');
-
- return
Hover me
;
+ return (
+
+ );
}
```
-## Data Attributes
+---
+
+## Built With
+
+- **React 18+** with modern hooks
+- **TypeScript** for type safety
+- **Floating UI** for positioning
+- **Storybook** for documentation
-| Attribute | Description | Example |
-| ------------------------- | ----------------------------------------- | ----------------------------- |
-| `data-tip` | Tooltip content | `data-tip="Hello"` |
-| `data-tip-id` | Element identifier for flows | `data-tip-id="welcome"` |
-| `data-tip-placement` | Position (top, bottom, left, right, etc.) | `data-tip-placement="bottom"` |
-| `data-tip-delay` | Show delay in ms | `data-tip-delay="500"` |
-| `data-tip-hide-delay` | Hide delay in ms | `data-tip-hide-delay="100"` |
-| `data-tip-disabled` | Disable tooltip | `data-tip-disabled` |
-| `data-tip-ellipsis` | Enable text truncation | `data-tip-ellipsis` |
-| `data-tip-max-lines` | Max lines before truncation | `data-tip-max-lines="2"` |
-| `data-tip-word-wrap` | Enable word wrapping | `data-tip-word-wrap` |
-| `data-tip-max-width` | Maximum width in pixels | `data-tip-max-width="300"` |
-| `data-tip-html` | Parse content as HTML | `data-tip-html` |
-| `data-tip-interactive` | Keep tooltip on hover | `data-tip-interactive` |
-| `data-tip-move` | Smooth move transition | `data-tip-move` |
-| `data-tip-jump` | Jump transition | `data-tip-jump` |
-| `data-tip-group` | Group identifier for transitions | `data-tip-group="nav"` |
-| `data-tip-no-arrow` | Hide tooltip arrow | `data-tip-no-arrow` |
-| `data-tip-always-visible` | Keep element visible during tour focus | `data-tip-always-visible` |
-
-## Documentation
-
-- [Architecture](./docs/ARCHITECTURE.md) - Technical design and decisions
-- [API Reference](./docs/API.md) - Complete API documentation
-- [Roadmap](./docs/ROADMAP.md) - Planned features and milestones
-
-## Tech Stack
-
-- **React 18+** - Modern React with hooks
-- **TypeScript** - Full type safety
-- **Floating UI** - Robust positioning engine
-- **Vite** - Fast development and building
-- **Vitest** - Unit and integration testing
-- **Storybook** - Component documentation and showcase
+---
## License
Apache-2.0
+
+---
+
+
+ Made with care for the React community
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 419c849..32757f1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,15 +1,16 @@
{
"name": "@galangel/react-tip-magic",
- "version": "1.0.2",
+ "version": "1.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@galangel/react-tip-magic",
- "version": "1.0.2",
+ "version": "1.0.3",
"license": "Apache-2.0",
"dependencies": {
- "@floating-ui/react": "^0.27.16"
+ "@floating-ui/react": "^0.27.16",
+ "fuzzysort": "^3.1.0"
},
"devDependencies": {
"@eslint/js": "^9.28.0",
@@ -4476,6 +4477,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/fuzzysort": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz",
+ "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==",
+ "license": "MIT"
+ },
"node_modules/generator-function": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
diff --git a/package.json b/package.json
index d56567a..655d5de 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@galangel/react-tip-magic",
- "version": "1.0.3",
+ "version": "1.1.0",
"description": "A sophisticated, elegant, and performant tooltip library for React with an intelligent floating helper system.",
"type": "module",
"main": "./dist/index.cjs",
@@ -60,7 +60,8 @@
"react-dom": ">=18.0.0"
},
"dependencies": {
- "@floating-ui/react": "^0.27.16"
+ "@floating-ui/react": "^0.27.16",
+ "fuzzysort": "^3.1.0"
},
"devDependencies": {
"@eslint/js": "^9.28.0",
diff --git a/src/KeyboardShortcuts.mdx b/src/KeyboardShortcuts.mdx
index 3cdef5c..cfef599 100644
--- a/src/KeyboardShortcuts.mdx
+++ b/src/KeyboardShortcuts.mdx
@@ -8,125 +8,91 @@ Display keyboard shortcuts alongside tooltip content for better discoverability
## Overview
-React Tip Magic automatically parses tooltip content to extract and style keyboard shortcuts. When a separator character is found in the tooltip content, everything after it is displayed as a styled keyboard shortcut badge.
+React Tip Magic displays keyboard shortcuts as styled badges next to tooltip text. Use the `data-tip-shortcut` attribute to specify the shortcut.
**Key features:**
-- Automatic parsing β no extra markup needed
-- Customizable separator (default: `;`)
+- Simple attribute-based API
- Styled `` element for the shortcut
- CSS custom properties for theming
-- Can be disabled globally or enabled per-element
+- Can be disabled globally
+- Works with TipAdvisor for shortcut discovery
---
## Quick Start
-Use a semicolon (`;`) to separate the main text from the keyboard shortcut:
+Use `data-tip-shortcut` to add a keyboard shortcut badge:
```tsx
-
-
-
+
+
+
```
-The library automatically:
-
-1. Splits the content at the separator
-2. Renders the main text normally
-3. Displays the shortcut in a styled `` badge
+The library displays:
+1. The main text from `data-tip`
+2. A styled `` badge with the shortcut
---
## API Reference
-### Provider Options
+### Data Attributes
-
Option
-
Type
-
Default
+
Attribute
Description
-
- contentSeparator
-
-
string
-
- ';'
-
-
Character(s) used to separate main text from keyboard shortcut
+
data-tip
+
Tooltip content (main text)
-
- enableShortcutStyle
-
-
boolean
-
- true
-
-
- Whether to render shortcuts with styled <kbd> badges
-
Whether to render shortcuts with styled <kbd> badges
```tsx
-{
- /* Uses custom separator for this element only */
-}
-;
-
-{
- /* Still uses default semicolon separator */
-}
-;
+
+
+
```
### CSS Classes
@@ -140,12 +106,8 @@ The library automatically:
-
- .tip-magic-shortcut
-
-
- Applied to the <kbd> element containing the shortcut
-
+
.tip-magic-shortcut
+
Applied to the <kbd> element containing the shortcut
@@ -162,21 +124,13 @@ The library automatically:
-
- --tip-magic-shortcut-bg
-
-
- rgba(255, 255, 255, 0.15)
-
+
--tip-magic-shortcut-bg
+
rgba(255, 255, 255, 0.15)
Background color of the shortcut badge
-
- --tip-magic-shortcut-text
-
-
- #e5e7eb
-
+
--tip-magic-shortcut-text
+
#e5e7eb
Text color of the shortcut badge
@@ -236,76 +190,44 @@ Common keyboard symbols for use in shortcuts:
-
- β
-
+
β
Command (Mac)
-
- βC for copy
-
+
βC for copy
-
- β₯
-
+
β₯
Option/Alt (Mac)
-
- β₯Tab for switch
-
+
β₯Tab for switch
-
- β§
-
+
β§
Shift
-
- β§Enter for new line
-
+
β§Enter for new line
-
- β
-
+
β
Control (Mac)
-
- βSpace for autocomplete
-
+
βSpace for autocomplete
-
- β΅
-
+
β΅
Enter/Return
-
- β΅ to submit
-
+
β΅ to submit
-
- β
-
+
β
Escape
-
- β to cancel
-
+
β to cancel
-
- β«
-
+
β«
Backspace/Delete
-
- β« to delete
-
+
β« to delete
-
- ββββ
-
+
ββββ
Arrow keys
-
- ββ to navigate
-
+
ββ to navigate
@@ -314,19 +236,17 @@ Common keyboard symbols for use in shortcuts:
## Examples & Patterns
-The following are implementation patterns showing how to use keyboard shortcut tooltips effectively.
-
### Pattern: Text Editor Toolbar
```tsx
-
@@ -336,15 +256,9 @@ The following are implementation patterns showing how to use keyboard shortcut t
```tsx
```
@@ -356,33 +270,25 @@ Detect the platform and show appropriate modifier keys:
const isMac = navigator.platform.includes('Mac');
const mod = isMac ? 'β' : 'Ctrl+';
-Copy
-Paste
-Save
+Copy
+Paste
+Save
```
-### Pattern: Ensuring Shortcuts Work
+### Pattern: With TipAdvisor
-Only display shortcuts that are actually implemented:
+Combine shortcuts with TipAdvisor for discoverability:
```tsx
-// Implement the shortcut handler
-useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if ((e.metaKey || e.ctrlKey) && e.key === 's') {
- e.preventDefault();
- handleSave();
- }
- };
-
- document.addEventListener('keydown', handleKeyDown);
- return () => document.removeEventListener('keydown', handleKeyDown);
-}, []);
-
-// Now it's safe to advertise the shortcut
-
- Save
-;
+
+
+ Copy
+ Paste
+
+
+ {/* Press F1 to see all shortcuts */}
+
+
```
---
@@ -394,11 +300,11 @@ useEffect(() => {
Place shortcuts on interactive elements users will naturally hover over:
```tsx
-// β Good: Toolbar buttons users interact with
-B
+// Good: Toolbar buttons users interact with
+B
-// β Avoid: Shortcut on non-interactive element
-Some text
+// Avoid: Shortcut on non-interactive element
+Some text
```
### Be Consistent
@@ -406,15 +312,15 @@ Place shortcuts on interactive elements users will naturally hover over:
Use the same format throughout your application:
```tsx
-// β Good: Consistent format
-Bold
-Italic
-Underline
-
-// β Avoid: Inconsistent format
-Bold
-Italic
-Underline
+// Good: Consistent format
+Bold
+Italic
+Underline
+
+// Avoid: Inconsistent format
+Bold
+Italic
+Underline
```
### Don't Overload
@@ -422,9 +328,9 @@ Use the same format throughout your application:
Not every action needs a keyboard shortcut displayed:
```tsx
-// β Good: Common, frequently used shortcuts
-Save
-Undo
+// Good: Common, frequently used shortcuts
+Save
+Undo
// Consider: Less common actions might not need shortcut display
Export
@@ -441,7 +347,12 @@ Keyboard shortcuts improve accessibility when implemented correctly:
Announce shortcuts to screen readers:
```tsx
-
+
Save
```
@@ -452,11 +363,12 @@ Announce shortcuts to screen readers:
- Document all shortcuts in a help section or keyboard shortcuts modal
- Avoid conflicts with browser or OS shortcuts
- Test with assistive technologies
+- Use TipAdvisor to make shortcuts discoverable
---
## Next Steps
- See the **[Keyboard Shortcuts](?path=/story/tooltip-keyboard-shortcuts--with-shortcuts)** stories for live demos
-- Explore **[Basic Tooltips](?path=/story/tooltip-basic--simple-tooltips)** for general tooltip usage
+- Explore **[TipAdvisor](?path=/docs/tooltip-tipadvisor-documentation--docs)** for shortcut discoverability
- Check the **[Getting Started](/docs/getting-started--docs)** guide for setup instructions
diff --git a/src/TipAdvisor.mdx b/src/TipAdvisor.mdx
new file mode 100644
index 0000000..c0edb45
--- /dev/null
+++ b/src/TipAdvisor.mdx
@@ -0,0 +1,1087 @@
+import { Meta } from '@storybook/addon-docs/blocks';
+
+
+
+# TipAdvisor
+
+A discoverable menu component with fuzzy search that displays all tooltips with keyboard shortcuts, allowing users to explore and trigger actions via keyboard or mouse.
+
+## Overview
+
+The TipAdvisor is an **optional, standalone component** that you add alongside `TipMagicProvider` when you want shortcut discoverability. It:
+
+- Collects all elements with `data-tip` and `data-tip-shortcut` attributes
+- Supports **preset items** for command palette patterns (no DOM elements needed)
+- Provides fuzzy search to filter shortcuts
+- Highlights matching text in search results
+- Shows tooltips on target elements when hovering menu items
+- Triggers clicks on target elements when selecting menu items
+
+**Key features:**
+
+- Keyboard activated (F1 by default)
+- Fuzzy search with highlighting (powered by fuzzysort)
+- Hover preview of tooltips on target elements
+- Click to trigger the target element
+- Preset items for command palette patterns
+- Arrow key navigation
+- Optional backdrop overlay
+- Fully themeable via CSS custom properties
+- Zero overhead when not used
+
+---
+
+## Quick Start
+
+```tsx
+import { TipMagicProvider, TipAdvisor } from 'react-tip-magic';
+
+function App() {
+ return (
+
+ {/* Your app with tooltip elements */}
+
+ Copy
+
+
+ Paste
+
+
+ {/* Add TipAdvisor - optional! */}
+
+
+ );
+}
+```
+
+Press **F1** to open the TipAdvisor menu, then start typing to search.
+
+---
+
+## API Reference
+
+### TipAdvisor Props
+
+
Close menu when clicking the backdrop (only applies if showBackdrop is true)
+
+
+
+ searchPlaceholder
+
+
string
+
+ 'Search shortcuts...'
+
+
Placeholder text for the search input
+
+
+
+ selector
+
+
string | null
+
+ '[data-tip][data-tip-shortcut]'
+
+
+ CSS selector for elements to include. Set to null to disable DOM scanning
+ (useful for preset-only menus)
+
+
+
+
+ items
+
+
TipAdvisorPresetItem[]
+
-
+
+ Preset items for command palette patterns. These are added alongside DOM-scanned items
+
+
+
+
+ className
+
+
string
+
-
+
Custom CSS class for the menu container
+
+
+
+ itemClassName
+
+
string
+
-
+
Custom CSS class for menu items
+
+
+
+ onOpen
+
+
function
+
-
+
Callback when menu opens
+
+
+
+ onClose
+
+
function
+
-
+
Callback when menu closes
+
+
+
+
+### useTipAdvisor Hook
+
+For programmatic control of the TipAdvisor:
+
+```tsx
+import { useTipAdvisor, TipAdvisor } from 'react-tip-magic';
+
+function MyComponent() {
+ const advisor = useTipAdvisor();
+
+ return (
+ <>
+ Open Shortcuts
+ Close
+ Toggle
+
+
+ >
+ );
+}
+```
+
+**Returns:**
+
+
+
+
+
Property
+
Type
+
Description
+
+
+
+
+
+ ref
+
+
React.RefObject
+
Ref to pass to TipAdvisor component
+
+
+
+ open
+
+
function
+
Open the TipAdvisor menu
+
+
+
+ close
+
+
function
+
Close the TipAdvisor menu
+
+
+
+ toggle
+
+
function
+
Toggle the TipAdvisor menu
+
+
+
+
+---
+
+## Preset Items
+
+For command palette patterns or actions not tied to DOM elements, use the `items` prop with `TipAdvisorPresetItem` objects:
+
+### TipAdvisorPresetItem
+
+
+
+
+
Property
+
Type
+
Required
+
Description
+
+
+
+
+
+ id
+
+
string
+
Yes
+
Unique identifier for the item
+
+
+
+ label
+
+
string
+
Yes
+
Text displayed in the menu
+
+
+
+ shortcut
+
+
string
+
No
+
Keyboard shortcut to display (optional)
+
+
+
+ onSelect
+
+
function
+
Yes
+
Callback executed when item is selected
+
+
+
+
+### Command Palette Example
+
+```tsx
+import { TipAdvisor, useTipAdvisor, TipAdvisorPresetItem } from 'react-tip-magic';
+
+function CommandPalette() {
+ const advisor = useTipAdvisor();
+
+ const commands: TipAdvisorPresetItem[] = [
+ {
+ id: 'new-file',
+ label: 'New File',
+ shortcut: 'βN',
+ onSelect: () => console.log('Creating new file...'),
+ },
+ {
+ id: 'open-file',
+ label: 'Open File',
+ shortcut: 'βO',
+ onSelect: () => console.log('Opening file picker...'),
+ },
+ {
+ id: 'save',
+ label: 'Save',
+ shortcut: 'βS',
+ onSelect: () => console.log('Saving...'),
+ },
+ {
+ id: 'settings',
+ label: 'Open Settings',
+ shortcut: 'β,',
+ onSelect: () => console.log('Opening settings...'),
+ },
+ ];
+
+ return (
+ <>
+
+ Quick Actions (βK)
+
+
+
+
+
+ >
+ );
+}
+```
+
+### Combining DOM Elements and Preset Items
+
+You can use both DOM-scanned elements and preset items together:
+
+```tsx
+
+```
+
+---
+
+## Fuzzy Search
+
+The TipAdvisor includes built-in fuzzy search powered by [fuzzysort](https://github.com/farzher/fuzzysort). When you start typing:
+
+- Results are filtered in real-time
+- Matching characters are highlighted
+- Both tooltip content and shortcuts are searchable
+- Typos and partial matches work
+
+### Search Behavior
+
+- The search input is autofocused when the menu opens
+- Type to filter the list instantly
+- Matching parts are highlighted with `` elements
+- Arrow keys navigate filtered results
+- Enter selects the focused item
+
+---
+
+## Keyboard Navigation
+
+
+
+
+
Key
+
Action
+
+
+
+
+
+ F1
+
+
+ Toggle the TipAdvisor menu (configurable via triggerKey)
+
+
+
+
+ Escape
+
+
Close the menu
+
+
+
+ Arrow Down
+
+
Move to next item
+
+
+
+ Arrow Up
+
+
Move to previous item
+
+
+
+ Enter
+
+
Select focused item (triggers click on target element)
+
+
+
Any character
+
Start searching (input is autofocused)
+
+
+
+
+---
+
+## CSS Custom Properties
+
+Override these CSS variables to customize the appearance:
+
+### Background Colors
+
+
+
+
+
Property
+
Default
+
Description
+
+
+
+
+
+ --tip-advisor-bg
+
+
+ rgba(24, 24, 27, 0.95)
+
+
Menu background color
+
+
+
+ --tip-advisor-backdrop-bg
+
+
+ rgba(0, 0, 0, 0.5)
+
+
Backdrop overlay color
+
+
+
+ --tip-advisor-item-bg
+
+
+ transparent
+
+
Item background (default state)
+
+
+
+ --tip-advisor-item-hover-bg
+
+
+ rgba(255, 255, 255, 0.1)
+
+
Item background on hover
+
+
+
+ --tip-advisor-item-focus-bg
+
+
+ rgba(255, 255, 255, 0.15)
+
+
Item background on keyboard focus
+
+
+
+
+### Text Colors
+
+
+
+
+
Property
+
Default
+
Description
+
+
+
+
+
+ --tip-advisor-text
+
+
+ #fafafa
+
+
Primary text color
+
+
+
+ --tip-advisor-text-secondary
+
+
+ #a1a1aa
+
+
Secondary/muted text color
+
+
+
+ --tip-advisor-shortcut-text
+
+
+ #e5e7eb
+
+
Shortcut badge text color
+
+
+
+ --tip-advisor-shortcut-bg
+
+
+ rgba(255, 255, 255, 0.15)
+
+
Shortcut badge background
+
+
+
+
+### Search Input
+
+
+
+
+
Property
+
Default
+
Description
+
+
+
+
+
+ --tip-advisor-search-bg
+
+
+ rgba(255, 255, 255, 0.1)
+
+
Search input background
+
+
+
+ --tip-advisor-search-border
+
+
+ rgba(255, 255, 255, 0.15)
+
+
Search input border color
+
+
+
+ --tip-advisor-search-focus-border
+
+
+ rgba(255, 255, 255, 0.3)
+
+
Search input border when focused
+
+
+
+ --tip-advisor-search-text
+
+
+ #fafafa
+
+
Search input text color
+
+
+
+ --tip-advisor-search-placeholder
+
+
+ #71717a
+
+
Search input placeholder color
+
+
+
+
+### Search Highlight
+
+
+
+
+
Property
+
Default
+
Description
+
+
+
+
+
+ --tip-advisor-highlight-bg
+
+
+ rgba(250, 204, 21, 0.4)
+
+
Highlighted match background
+
+
+
+ --tip-advisor-highlight-text
+
+
+ inherit
+
+
Highlighted match text color
+
+
+
+
+### Border & Shape
+
+
+
+
+
Property
+
Default
+
Description
+
+
+
+
+
+ --tip-advisor-border
+
+
+ 1px solid rgba(255, 255, 255, 0.1)
+
+
Menu border
+
+
+
+ --tip-advisor-border-radius
+
+
+ 12px
+
+
Menu border radius
+
+
+
+ --tip-advisor-item-border-radius
+
+
+ 8px
+
+
Item border radius
+
+
+
+ --tip-advisor-shadow
+
+
+ 0 25px 50px -12px rgba(0, 0, 0, 0.5)
+
+
Menu shadow
+
+
+
+
+### Sizing
+
+
+
+
+
Property
+
Default
+
Description
+
+
+
+
+
+ --tip-advisor-width
+
+
+ 320px
+
+
Menu width
+
+
+
+ --tip-advisor-max-height
+
+
+ 400px
+
+
Maximum menu height (scrollable)
+
+
+
+ --tip-advisor-padding
+
+
+ 8px
+
+
Menu padding
+
+
+
+ --tip-advisor-item-padding
+
+
+ 10px 12px
+
+
Item padding
+
+
+
+ --tip-advisor-gap
+
+
+ 4px
+
+
Gap between items
+
+
+
+ --tip-advisor-animation-duration
+
+
+ 150ms
+
+
Animation duration
+
+
+
+
+---
+
+## CSS Classes Reference
+
+Override these classes for custom styling:
+
+
+
+
+```
+
+### Without Backdrop
+
+```tsx
+
+```
+
+### Custom Search Placeholder
+
+```tsx
+
+```
+
+### Custom Highlight Styling
+
+```css
+:root {
+ /* Make highlights more prominent with a blue color */
+ --tip-advisor-highlight-bg: rgba(59, 130, 246, 0.5);
+ --tip-advisor-highlight-text: #ffffff;
+}
+```
+
+### Command Palette Pattern
+
+Use preset items for a command palette that doesn't rely on DOM elements:
+
+```tsx
+const advisor = useTipAdvisor();
+
+const commands: TipAdvisorPresetItem[] = [
+ { id: 'new', label: 'New File', shortcut: 'βN', onSelect: () => createFile() },
+ { id: 'open', label: 'Open File', shortcut: 'βO', onSelect: () => openFile() },
+ { id: 'save', label: 'Save', shortcut: 'βS', onSelect: () => saveFile() },
+];
+
+
+ Quick Actions (βK)
+
+
+
+
+
+```
+
+### Custom Toggle Button
+
+```tsx
+const advisor = useTipAdvisor();
+
+Show Shortcuts
+Toggle (F1)
+
+
+```
+
+---
+
+## Accessibility
+
+The TipAdvisor is built with accessibility in mind:
+
+- **Role attributes**: Uses `role="dialog"`, `role="menu"`, `role="menuitem"`
+- **ARIA labels**: `aria-modal="true"`, `aria-label` on dialog and search input
+- **Keyboard navigation**: Full keyboard support
+- **Focus management**: Search input is autofocused when menu opens
+- **Reduced motion**: Respects `prefers-reduced-motion`
+
+---
+
+## Best Practices
+
+### When to Use
+
+- Applications with many keyboard shortcuts
+- Power user features
+- Toolbar-heavy interfaces
+- Documentation/help systems
+- Command palette functionality
+
+### DOM Scanning vs Preset Items
+
+**Use DOM scanning (default)** when:
+
+- Shortcuts are tied to visible UI elements
+- You want tooltips to appear on the target elements
+- Clicking should trigger the actual element
+
+**Use preset items** when:
+
+- Building a command palette with actions not tied to UI
+- Actions are contextual or dynamically generated
+- You want full control over what happens when an item is selected
+
+**Combine both** for the best of both worlds:
+
+```tsx
+
+```
+
+### What Elements to Include
+
+- Buttons with actions
+- Navigation links
+- Toolbar buttons
+- Form controls with keyboard shortcuts
+
+### Don't Overload
+
+Not every action needs a keyboard shortcut. Focus on:
+
+- Frequently used actions
+- Actions that benefit from quick access
+- Standard shortcuts (copy, paste, save, etc.)
+
+---
+
+## Next Steps
+
+- See the **[TipAdvisor Playground](?path=/story/the-tipadvisor--the-tip-advisor)** for interactive demo
+- Explore **[Use Case Stories](?path=/story/tooltip-tipadvisor--toolbar-shortcuts)** for real-world examples
+- Check the **[Keyboard Shortcuts Guide](?path=/docs/tooltip-keyboard-shortcuts-documentation--docs)** for tooltip shortcuts
diff --git a/src/Welcome.mdx b/src/Welcome.mdx
index 5bee3d2..d0e2c3d 100644
--- a/src/Welcome.mdx
+++ b/src/Welcome.mdx
@@ -6,62 +6,76 @@ import { Meta } from '@storybook/addon-docs/blocks';
β¨ React Tip Magic
-
Sophisticated, elegant, and performant tooltips for React
+
Thoughtfully crafted tooltips for React
## Welcome
-**React Tip Magic** is a modern tooltip library designed with both user experience and developer experience in mind. It provides a simple way to add beautiful, accessible tooltips to your React applications with minimal configuration.
+React Tip Magic is a tooltip library built with care. It handles the small details that make interfaces feel polishedβsmooth transitions, proper positioning, keyboard accessibilityβso you can focus on building your application.
-### Key Features
+Whether you need simple tooltips, guided tours, or a way for users to discover keyboard shortcuts, React Tip Magic provides clean, declarative APIs that stay out of your way.
+
+---
+
+## Features
-
π― Zero Config
-
Just add data-tip="Hello" to any element. No wrapper components needed.
+
π― Tooltips
+
Add data-tip="Hello" to any element. One tooltip instance moves gracefully between targets.
-
-
π High Performance
-
Single global instance with event delegation. Minimal re-renders and memory footprint.
-
+
+
β¨οΈ Keyboard Shortcuts
+
Display shortcuts with data-tip-shortcut="βS". Styled badges that feel native to your UI.
+
-
-
π¨ Smooth Transitions
-
Tooltips gracefully animate between elements when hovering from one to another.
-
+
+
πΊοΈ Guided Tours
+
Walk users through your interface step by step. Great for onboarding or introducing new features.
+
-
-
π€ Intelligent Helper
-
- Optional floating assistant with multiple states for onboarding, actions, and contextual help.
-
-
+
+
π Tip Advisor
+
A searchable menu of all shortcuts in your app. Press F1 to discover what's available.
+
-
-
π± Fully Accessible
-
Keyboard navigation, screen reader support, and respects reduced motion preferences.
-
+
+
π¨ Smooth Transitions
+
Tooltips animate between elements. Group related items for cohesive movement.
+
-
π Customizable
-
Extensive theming via CSS custom properties. Light, dark, and auto themes included.
+
π± Accessible
+
Keyboard navigation, screen reader support, and respects reduced motion preferences.
+
+
+
+
π Performant
+
Single global instance with event delegation. Minimal re-renders and memory footprint.
+
+
+
+
π Themeable
+
CSS custom properties for easy customization. Adapts to light, dark, or custom themes.
+---
+
## Quick Start
### Installation
```bash
-npm install react-tip-magic
+npm install @galangel/react-tip-magic
```
-### Basic Setup
+### Setup
```tsx
-import { TipMagicProvider } from 'react-tip-magic';
-import 'react-tip-magic/styles.css';
+import { TipMagicProvider } from '@galangel/react-tip-magic';
+import '@galangel/react-tip-magic/styles.css';
function App() {
return (
@@ -72,47 +86,47 @@ function App() {
}
```
-### Simple Tooltip
+### Add Tooltips
```tsx
-Save
-```
+Save
-### With Keyboard Shortcuts
-
-```tsx
-Copy
+{/* With keyboard shortcut */}
+Copy
```
+---
+
## Architecture
-React Tip Magic uses a **global singleton pattern** where:
+React Tip Magic uses a global singleton pattern:
-1. A single `` is mounted at your app's root
-2. Elements opt-in via `data-tip` attributes
-3. Event delegation handles all hover/focus interactions
-4. A single tooltip instance moves between targets
+1. Mount `` at your app's root
+2. Add `data-tip` attributes to elements
+3. Event delegation handles interactions
+4. A single tooltip moves between targets
-This approach ensures:
+This means:
+- Minimal DOM nodes
+- Smooth transitions between tooltips
+- Works with dynamically added elements
+- No wrapper components in your JSX
-- β Minimal DOM nodes
-- β Smooth transitions between tooltips
-- β Works with dynamically added elements
-- β No wrapper components polluting your JSX
+---
-## What's Next?
+## Explore
-Explore the sidebar to learn more:
+Use the sidebar to learn more:
-- **Getting Started** - Detailed setup and configuration
-- **Components** - Tooltip and Helper components
-- **Hooks** - The `useTipMagic` hook API
-- **Examples** - Real-world usage patterns
+- **[The Tooltip](?path=/story/the-tooltip--the-tooltip)** β Interactive playground
+- **[Keyboard Shortcuts](?path=/docs/tooltip-keyboard-shortcuts-documentation--docs)** β Displaying shortcuts
+- **[The Tour](?path=/story/the-tour--the-tour)** β Guided walkthroughs
+- **[Tip Advisor](?path=/docs/tooltip-tipadvisor-documentation--docs)** β Shortcut discovery
---
- Made with β€οΈ for the React community
+ Made with care for the React community
+ );
+};
+
+/**
+ * Interactive TipAdvisor playground.
+ *
+ * The menu is shown automatically - use the controls panel to experiment
+ * with all available options. Try typing in the search box to filter shortcuts!
+ */
+export const TheTipAdvisor: Story = {
+ args: {
+ position: 'center',
+ triggerKey: 'F1',
+ showCloseButton: true,
+ showBackdrop: true,
+ closeOnBackdropClick: true,
+ searchPlaceholder: 'Search shortcuts...',
+ itemCount: 8,
+ },
+ argTypes: {
+ position: {
+ control: 'select',
+ options: ['center', 'bottom-right', 'bottom-left', 'top-right', 'top-left'],
+ description: 'Position of the menu on screen',
+ },
+ triggerKey: {
+ control: 'text',
+ description: 'Key to toggle the TipAdvisor menu',
+ },
+ showCloseButton: {
+ control: 'boolean',
+ description: 'Show close button in header',
+ },
+ showBackdrop: {
+ control: 'boolean',
+ description: 'Show backdrop overlay behind the menu',
+ },
+ closeOnBackdropClick: {
+ control: 'boolean',
+ description: 'Close when clicking the backdrop',
+ },
+ searchPlaceholder: {
+ control: 'text',
+ description: 'Placeholder text for the search input',
+ },
+ itemCount: {
+ control: { type: 'range', min: 1, max: 8, step: 1 },
+ description: 'Number of items in the demo toolbar',
+ },
+ },
+ render: (args) => (
+
+
+
+ ),
+};
diff --git a/src/stories/Tooltip/TheTooltip.stories.tsx b/src/stories/Tooltip/TheTooltip.stories.tsx
index 334a49b..61e5bd1 100644
--- a/src/stories/Tooltip/TheTooltip.stories.tsx
+++ b/src/stories/Tooltip/TheTooltip.stories.tsx
@@ -11,7 +11,7 @@ import './tooltip-stories.css';
* ## Features
* - **Single instance**: One tooltip moves between targets for smooth transitions
* - **Data attribute based**: Just add `data-tip` to any element
- * - **Keyboard shortcut support**: Use `; ` to separate content from shortcuts
+ * - **Keyboard shortcut support**: Use `data-tip-shortcut` attribute for shortcuts
* - **Floating UI powered**: Smart positioning with flip/shift
* - **Accessible**: Keyboard and screen reader support
* - **TypeScript support**: Use `getTipProps()` for typed tooltip configuration
@@ -58,13 +58,13 @@ export const TheTooltip: Story = {
transitionBehavior: 'jump',
moveTransitionDuration: 100,
showArrow: true,
- contentSeparator: ';',
+ shortcut: '',
showOnFocus: false,
},
argTypes: {
tip: {
control: 'text',
- description: 'Tooltip content. Use "; " to add keyboard shortcuts (e.g., "Save; βS")',
+ description: 'Tooltip content text',
},
placement: {
control: 'select',
@@ -139,10 +139,9 @@ export const TheTooltip: Story = {
control: 'boolean',
description: 'Show or hide the tooltip arrow',
},
- contentSeparator: {
+ shortcut: {
control: 'text',
- description:
- 'Character(s) to separate main text from keyboard shortcut (e.g., "Save; βS" uses ";")',
+ description: 'Keyboard shortcut to display (e.g., "βS")',
},
showOnFocus: {
control: 'boolean',
@@ -152,6 +151,14 @@ export const TheTooltip: Story = {
render: (args) => {
const tipProps = getTipProps(args);
+ // Filter out empty/default values for cleaner code preview
+ const displayArgs = Object.fromEntries(
+ Object.entries(args).filter(([key, value]) => {
+ if (key === 'shortcut' && value === '') return false;
+ return true;
+ })
+ );
+
return (
@@ -159,6 +166,8 @@ export const TheTooltip: Story = {
Using getTipProps() for typed tooltip configuration.
Adjust the controls below to see different tooltip behaviors.
+
+ Try adding a shortcut like "βS" or "Ctrl+C" to see it styled!
Hover me
@@ -167,7 +176,7 @@ export const TheTooltip: Story = {
Hover me
+ );
+};
+
+export const DualAdvisors: Story = {
+ render: () => (
+
+
+
+ ),
+};
diff --git a/src/styles/index.css b/src/styles/index.css
index 1dc5075..3c8f1cb 100644
--- a/src/styles/index.css
+++ b/src/styles/index.css
@@ -11,3 +11,6 @@
/* Tour styles (navigation, images, backdrop) */
@import './tour.css';
+
+/* TipAdvisor styles (optional menu component) */
+@import './tip-advisor.css';
diff --git a/src/styles/tip-advisor.css b/src/styles/tip-advisor.css
new file mode 100644
index 0000000..36006f7
--- /dev/null
+++ b/src/styles/tip-advisor.css
@@ -0,0 +1,552 @@
+/**
+ * TipAdvisor Styles
+ *
+ * Styles for the optional TipAdvisor menu component that displays
+ * all tooltips with keyboard shortcuts for discoverability.
+ *
+ * All colors and sizing are controlled via CSS custom properties.
+ * Override these in your own stylesheet to customize the appearance.
+ */
+
+/* ==========================================================================
+ CSS CUSTOM PROPERTIES
+ ========================================================================== */
+
+:root {
+ /* --------------------------------------------------------------------------
+ BACKGROUND COLORS
+ -------------------------------------------------------------------------- */
+
+ /**
+ * Background color of the menu panel
+ * @default Semi-transparent dark gray
+ */
+ --tip-advisor-bg: rgba(24, 24, 27, 0.95);
+
+ /**
+ * Background color of the backdrop overlay
+ * @default Semi-transparent black
+ */
+ --tip-advisor-backdrop-bg: rgba(0, 0, 0, 0.5);
+
+ /**
+ * Background color of menu items (default state)
+ * @default Transparent
+ */
+ --tip-advisor-item-bg: transparent;
+
+ /**
+ * Background color of menu items on hover
+ * @default Semi-transparent white
+ */
+ --tip-advisor-item-hover-bg: rgba(255, 255, 255, 0.1);
+
+ /**
+ * Background color of menu items on keyboard focus
+ * @default Slightly more opaque white
+ */
+ --tip-advisor-item-focus-bg: rgba(255, 255, 255, 0.15);
+
+ /* --------------------------------------------------------------------------
+ TEXT COLORS
+ -------------------------------------------------------------------------- */
+
+ /**
+ * Primary text color
+ * @default Near-white
+ */
+ --tip-advisor-text: #fafafa;
+
+ /**
+ * Secondary/muted text color
+ * @default Light gray
+ */
+ --tip-advisor-text-secondary: #a1a1aa;
+
+ /**
+ * Text color for shortcut badges
+ * @default Light gray
+ */
+ --tip-advisor-shortcut-text: #e5e7eb;
+
+ /**
+ * Background color for shortcut badges
+ * @default Semi-transparent white
+ */
+ --tip-advisor-shortcut-bg: rgba(255, 255, 255, 0.15);
+
+ /* --------------------------------------------------------------------------
+ BORDER & SHAPE
+ -------------------------------------------------------------------------- */
+
+ /**
+ * Border style for the menu panel
+ * @default Subtle white border
+ */
+ --tip-advisor-border: 1px solid rgba(255, 255, 255, 0.1);
+
+ /**
+ * Border radius for the menu panel
+ * @default 12px (rounded corners)
+ */
+ --tip-advisor-border-radius: 12px;
+
+ /**
+ * Border radius for menu items
+ * @default 8px
+ */
+ --tip-advisor-item-border-radius: 8px;
+
+ /* --------------------------------------------------------------------------
+ SHADOWS
+ -------------------------------------------------------------------------- */
+
+ /**
+ * Box shadow for the menu panel
+ * @default Large soft shadow
+ */
+ --tip-advisor-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
+
+ /* --------------------------------------------------------------------------
+ SIZING
+ -------------------------------------------------------------------------- */
+
+ /**
+ * Width of the menu panel
+ * @default 320px
+ */
+ --tip-advisor-width: 320px;
+
+ /**
+ * Maximum height of the menu panel (scrollable if exceeded)
+ * @default 400px
+ */
+ --tip-advisor-max-height: 400px;
+
+ /**
+ * Padding inside the menu panel
+ * @default 8px
+ */
+ --tip-advisor-padding: 8px;
+
+ /**
+ * Padding inside menu items
+ * @default 10px 12px
+ */
+ --tip-advisor-item-padding: 10px 12px;
+
+ /**
+ * Gap between menu items
+ * @default 0px
+ */
+ --tip-advisor-gap: 0px;
+
+ /* --------------------------------------------------------------------------
+ ANIMATION
+ -------------------------------------------------------------------------- */
+
+ /**
+ * Duration of show/hide animations
+ * @default 150ms
+ */
+ --tip-advisor-animation-duration: 150ms;
+
+ /* --------------------------------------------------------------------------
+ SEARCH INPUT
+ -------------------------------------------------------------------------- */
+
+ /**
+ * Background color for the search input
+ * @default Semi-transparent white
+ */
+ --tip-advisor-search-bg: rgba(255, 255, 255, 0.1);
+
+ /**
+ * Border color for the search input
+ * @default Semi-transparent white
+ */
+ --tip-advisor-search-border: rgba(255, 255, 255, 0.15);
+
+ /**
+ * Border color for the search input when focused
+ * @default Semi-transparent white (more visible)
+ */
+ --tip-advisor-search-focus-border: rgba(255, 255, 255, 0.3);
+
+ /**
+ * Text color for the search input
+ * @default Near-white
+ */
+ --tip-advisor-search-text: #fafafa;
+
+ /**
+ * Placeholder text color for the search input
+ * @default Light gray
+ */
+ --tip-advisor-search-placeholder: #71717a;
+
+ /* --------------------------------------------------------------------------
+ HIGHLIGHT (SEARCH MATCH)
+ -------------------------------------------------------------------------- */
+
+ /**
+ * Background color for highlighted/matched text
+ * @default Semi-transparent yellow
+ */
+ --tip-advisor-highlight-bg: rgba(250, 204, 21, 0.4);
+
+ /**
+ * Text color for highlighted/matched text
+ * @default Inherit (same as surrounding text)
+ */
+ --tip-advisor-highlight-text: inherit;
+}
+
+/* ==========================================================================
+ MAIN CONTAINER
+ ========================================================================== */
+
+.tip-advisor {
+ position: fixed;
+ inset: 0;
+ z-index: 10000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* ==========================================================================
+ BACKDROP
+ ========================================================================== */
+
+.tip-advisor-backdrop {
+ position: absolute;
+ inset: 0;
+ background: var(--tip-advisor-backdrop-bg);
+ animation: tip-advisor-fade-in var(--tip-advisor-animation-duration) ease-out;
+}
+
+@keyframes tip-advisor-fade-in {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+/* ==========================================================================
+ MENU PANEL
+ ========================================================================== */
+
+.tip-advisor-menu {
+ position: relative;
+ width: var(--tip-advisor-width);
+ max-height: var(--tip-advisor-max-height);
+ background: var(--tip-advisor-bg);
+ border: var(--tip-advisor-border);
+ border-radius: var(--tip-advisor-border-radius);
+ box-shadow: var(--tip-advisor-shadow);
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ animation: tip-advisor-scale-in var(--tip-advisor-animation-duration) ease-out;
+}
+
+@keyframes tip-advisor-scale-in {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+/* ==========================================================================
+ POSITION VARIANTS
+ ========================================================================== */
+
+/* Center (default) - already centered via flexbox */
+.tip-advisor-menu--center {
+ /* No additional styles needed */
+}
+
+/* Bottom right */
+.tip-advisor-menu--bottom-right {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ top: auto;
+ left: auto;
+}
+
+/* Bottom left */
+.tip-advisor-menu--bottom-left {
+ position: fixed;
+ bottom: 20px;
+ left: 20px;
+ top: auto;
+ right: auto;
+}
+
+/* Top right */
+.tip-advisor-menu--top-right {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ bottom: auto;
+ left: auto;
+}
+
+/* Top left */
+.tip-advisor-menu--top-left {
+ position: fixed;
+ top: 20px;
+ left: 20px;
+ bottom: auto;
+ right: auto;
+}
+
+/* Top (centered horizontally) */
+.tip-advisor-menu--top {
+ position: fixed;
+ top: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ bottom: auto;
+ right: auto;
+ animation: tip-advisor-scale-in-centered var(--tip-advisor-animation-duration) ease-out;
+}
+
+/* Bottom (centered horizontally) */
+.tip-advisor-menu--bottom {
+ position: fixed;
+ bottom: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ top: auto;
+ right: auto;
+ animation: tip-advisor-scale-in-centered var(--tip-advisor-animation-duration) ease-out;
+}
+
+/* Animation for horizontally centered positions (preserves translateX) */
+@keyframes tip-advisor-scale-in-centered {
+ from {
+ opacity: 0;
+ transform: translateX(-50%) scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(-50%) scale(1);
+ }
+}
+
+/* ==========================================================================
+ HEADER
+ ========================================================================== */
+
+.tip-advisor-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px;
+ border-bottom: var(--tip-advisor-border);
+ flex-shrink: 0;
+}
+
+/* ==========================================================================
+ SEARCH INPUT
+ ========================================================================== */
+
+.tip-advisor-search {
+ flex: 1;
+ height: 36px;
+ padding: 0 12px;
+ background: var(--tip-advisor-search-bg);
+ border: 1px solid var(--tip-advisor-search-border);
+ border-radius: 8px;
+ color: var(--tip-advisor-search-text);
+ font-size: 13px;
+ font-family:
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+ outline: none;
+ transition:
+ border-color 150ms ease,
+ background-color 150ms ease;
+}
+
+.tip-advisor-search:focus {
+ border-color: var(--tip-advisor-search-focus-border);
+ background: rgba(255, 255, 255, 0.15);
+}
+
+.tip-advisor-search::placeholder {
+ color: var(--tip-advisor-search-placeholder);
+}
+
+.tip-advisor-close {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ padding: 0;
+ border: none;
+ background: transparent;
+ color: var(--tip-advisor-text-secondary);
+ font-size: 18px;
+ line-height: 1;
+ cursor: pointer;
+ border-radius: 4px;
+ transition:
+ background-color 150ms ease,
+ color 150ms ease;
+}
+
+.tip-advisor-close:hover {
+ background: var(--tip-advisor-item-hover-bg);
+ color: var(--tip-advisor-text);
+}
+
+.tip-advisor-close:focus {
+ outline: none;
+ background: var(--tip-advisor-item-focus-bg);
+ color: var(--tip-advisor-text);
+}
+
+/* ==========================================================================
+ LIST CONTAINER
+ ========================================================================== */
+
+.tip-advisor-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: var(--tip-advisor-padding);
+ display: flex;
+ flex-direction: column;
+ gap: var(--tip-advisor-gap);
+}
+
+/* Custom scrollbar */
+.tip-advisor-list::-webkit-scrollbar {
+ width: 6px;
+}
+
+.tip-advisor-list::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.tip-advisor-list::-webkit-scrollbar-thumb {
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 3px;
+}
+
+.tip-advisor-list::-webkit-scrollbar-thumb:hover {
+ background: rgba(255, 255, 255, 0.3);
+}
+
+/* ==========================================================================
+ MENU ITEMS
+ ========================================================================== */
+
+.tip-advisor-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--tip-advisor-item-padding);
+ background: var(--tip-advisor-item-bg);
+ border-radius: var(--tip-advisor-item-border-radius);
+ cursor: pointer;
+ transition: background-color 150ms ease;
+ user-select: none;
+}
+
+.tip-advisor-item:hover,
+.tip-advisor-item--hover {
+ background: var(--tip-advisor-item-hover-bg);
+}
+
+.tip-advisor-item--focus {
+ background: var(--tip-advisor-item-focus-bg);
+}
+
+.tip-advisor-item-content {
+ font-size: 13px;
+ color: var(--tip-advisor-text);
+ font-family:
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.tip-advisor-item-shortcut {
+ display: inline-flex;
+ align-items: center;
+ padding: 3px 6px;
+ margin-left: 12px;
+ background: var(--tip-advisor-shortcut-bg);
+ color: var(--tip-advisor-shortcut-text);
+ border-radius: 4px;
+ font-size: 11px;
+ line-height: 1;
+ font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
+ font-weight: 500;
+ letter-spacing: 0.02em;
+ flex-shrink: 0;
+}
+
+/* ==========================================================================
+ EMPTY STATE
+ ========================================================================== */
+
+.tip-advisor-empty {
+ padding: 24px 16px;
+ text-align: center;
+ color: var(--tip-advisor-text-secondary);
+ font-size: 13px;
+ font-family:
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+}
+
+/* ==========================================================================
+ SEARCH HIGHLIGHT
+ ========================================================================== */
+
+.tip-advisor-item-content mark,
+.tip-advisor-item-shortcut mark {
+ background: var(--tip-advisor-highlight-bg);
+ color: var(--tip-advisor-highlight-text);
+ border-radius: 2px;
+ padding: 0 1px;
+}
+
+/* ==========================================================================
+ NO BACKDROP VARIANT
+ ========================================================================== */
+
+.tip-advisor--no-backdrop {
+ pointer-events: none;
+}
+
+.tip-advisor--no-backdrop .tip-advisor-menu {
+ pointer-events: auto;
+}
+
+/* ==========================================================================
+ ACCESSIBILITY
+ ========================================================================== */
+
+@media (prefers-reduced-motion: reduce) {
+ .tip-advisor-backdrop,
+ .tip-advisor-menu {
+ animation: none;
+ }
+
+ .tip-advisor-item,
+ .tip-advisor-close {
+ transition: none;
+ }
+}
diff --git a/src/types/index.ts b/src/types/index.ts
index fd37091..73d663a 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -83,8 +83,6 @@ export interface TipMagicOptions {
disabled?: boolean;
/** Portal container element */
portalContainer?: HTMLElement;
- /** Separator for tooltip content parsing */
- contentSeparator?: string;
/** Enable keyboard shortcut styling */
enableShortcutStyle?: boolean;
/** Respect prefers-reduced-motion */
@@ -140,8 +138,8 @@ export interface TooltipShowOptions {
moveTransitionDuration?: number;
/** Show/hide arrow */
showArrow?: boolean;
- /** Content separator for keyboard shortcuts */
- contentSeparator?: string;
+ /** Keyboard shortcut to display */
+ shortcut?: string;
}
/**
@@ -310,8 +308,8 @@ export interface ParsedTooltipData {
moveTransitionDuration?: number;
/** Show or hide the arrow (default: true) */
showArrow: boolean;
- /** Override content separator for keyboard shortcuts (default: ';') */
- contentSeparator?: string;
+ /** Keyboard shortcut parsed from data-tip-shortcut attribute */
+ shortcut?: string;
/** Group identifier for controlling move transitions between grouped elements */
group?: string;
/** Whether to show tooltip when element receives focus */
@@ -381,3 +379,15 @@ export type {
TourStep,
UseTourReturn,
} from './tour';
+
+// =============================================================================
+// TipAdvisor Types
+// =============================================================================
+
+export type {
+ TipAdvisorAPI,
+ TipAdvisorItem,
+ TipAdvisorPosition,
+ TipAdvisorPresetItem,
+ TipAdvisorProps,
+} from './tipAdvisor';
diff --git a/src/types/tipAdvisor.ts b/src/types/tipAdvisor.ts
new file mode 100644
index 0000000..2106aa7
--- /dev/null
+++ b/src/types/tipAdvisor.ts
@@ -0,0 +1,103 @@
+/**
+ * TipAdvisor Types
+ *
+ * Types for the optional TipAdvisor component that displays a menu
+ * of all tooltips with keyboard shortcuts for discoverability.
+ */
+
+/**
+ * Position options for the TipAdvisor menu
+ */
+export type TipAdvisorPosition =
+ | 'center'
+ | 'top'
+ | 'bottom'
+ | 'bottom-right'
+ | 'bottom-left'
+ | 'top-right'
+ | 'top-left';
+
+/**
+ * A preset item that can be added to TipAdvisor without being tied to a DOM element.
+ * Useful for command palette-style menus with custom actions.
+ */
+export interface TipAdvisorPresetItem {
+ /** Unique identifier for the item */
+ id: string;
+ /** Label/content to display */
+ label: string;
+ /** Optional keyboard shortcut to display */
+ shortcut?: string;
+ /** Callback executed when the item is clicked */
+ onSelect: () => void;
+}
+
+/**
+ * Represents a single item in the TipAdvisor menu.
+ * Can be either element-based (from DOM) or preset (from props).
+ */
+export interface TipAdvisorItem {
+ /** Unique identifier (from data-tip-id or generated) */
+ id: string;
+ /** The DOM element this item refers to (undefined for preset items) */
+ element?: Element;
+ /** Tooltip content / label */
+ content: string;
+ /** Keyboard shortcut (from data-tip-shortcut or preset) */
+ shortcut?: string;
+ /** Callback for preset items (undefined for element-based items) */
+ onSelect?: () => void;
+}
+
+/**
+ * Props for the TipAdvisor component
+ */
+export interface TipAdvisorProps {
+ /** Key to trigger the advisor (default: 'F1') */
+ triggerKey?: string;
+ /** Position on screen (default: 'center') */
+ position?: TipAdvisorPosition;
+ /** Show close button in header (default: true) */
+ showCloseButton?: boolean;
+ /** Show backdrop overlay behind the menu (default: true) */
+ showBackdrop?: boolean;
+ /** Close when clicking backdrop (default: true, only applies if showBackdrop is true) */
+ closeOnBackdropClick?: boolean;
+ /** Custom CSS class for the menu container */
+ className?: string;
+ /** Custom CSS class for menu items */
+ itemClassName?: string;
+ /**
+ * Selector for elements to include (default: '[data-tip][data-tip-shortcut]').
+ * Set to empty string or null to disable DOM scanning (useful for preset-only menus).
+ */
+ selector?: string | null;
+ /** Callback when advisor opens */
+ onOpen?: () => void;
+ /** Callback when advisor closes */
+ onClose?: () => void;
+ /** Placeholder text for the search input (default: 'Search shortcuts...') */
+ searchPlaceholder?: string;
+ /**
+ * Preset items to display in the menu.
+ * These are command palette-style items not tied to DOM elements.
+ * They appear alongside any DOM-scanned items.
+ */
+ items?: TipAdvisorPresetItem[];
+}
+
+/**
+ * API exposed by TipAdvisor via ref for programmatic control
+ */
+export interface TipAdvisorAPI {
+ /** Open the TipAdvisor menu */
+ open: () => void;
+ /** Close the TipAdvisor menu */
+ close: () => void;
+ /** Toggle the TipAdvisor menu */
+ toggle: () => void;
+ /** Whether the menu is currently open */
+ isOpen: boolean;
+ /** Current list of items in the menu */
+ items: TipAdvisorItem[];
+}
diff --git a/src/utils/__tests__/getTipProps.test.ts b/src/utils/__tests__/getTipProps.test.ts
index c631a7c..1fee86b 100644
--- a/src/utils/__tests__/getTipProps.test.ts
+++ b/src/utils/__tests__/getTipProps.test.ts
@@ -133,17 +133,28 @@ describe('getTipProps', () => {
});
});
- describe('contentSeparator property', () => {
- it('should include separator when provided', () => {
- const result = getTipProps({ tip: 'Hello', contentSeparator: '|' });
- expect(result['data-tip-separator']).toBe('|');
+ describe('shortcut property', () => {
+ it('should include shortcut when provided', () => {
+ const result = getTipProps({ tip: 'Copy', shortcut: 'βC' });
+ expect(result['data-tip-shortcut']).toBe('βC');
+ });
+
+ it('should not include shortcut when empty string', () => {
+ const result = getTipProps({ tip: 'Hello', shortcut: '' });
+ expect(result['data-tip-shortcut']).toBeUndefined();
+ });
+
+ it('should not include shortcut when not provided', () => {
+ const result = getTipProps({ tip: 'Hello' });
+ expect(result['data-tip-shortcut']).toBeUndefined();
});
});
describe('complex scenarios', () => {
it('should handle all properties together', () => {
const result = getTipProps({
- tip: 'Save; βS',
+ tip: 'Save',
+ shortcut: 'βS',
id: 'save-btn',
placement: 'bottom',
showDelay: 100,
@@ -157,7 +168,8 @@ describe('getTipProps', () => {
moveTransitionDuration: 150,
});
- expect(result['data-tip']).toBe('Save; βS');
+ expect(result['data-tip']).toBe('Save');
+ expect(result['data-tip-shortcut']).toBe('βS');
expect(result['data-tip-id']).toBe('save-btn');
expect(result['data-tip-placement']).toBe('bottom');
expect(result['data-tip-delay']).toBe('100');
diff --git a/src/utils/__tests__/parseDataAttributes.test.ts b/src/utils/__tests__/parseDataAttributes.test.ts
index abb646c..119f067 100644
--- a/src/utils/__tests__/parseDataAttributes.test.ts
+++ b/src/utils/__tests__/parseDataAttributes.test.ts
@@ -1,57 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { generateTooltipId, parseContent } from '../parseDataAttributes';
-
-describe('parseContent', () => {
- describe('with default separator (;)', () => {
- it('should return main content when no shortcut is present', () => {
- const result = parseContent('Save changes');
- expect(result).toEqual({ main: 'Save changes' });
- });
-
- it('should extract shortcut after separator', () => {
- const result = parseContent('Copy; βC');
- expect(result).toEqual({ main: 'Copy', shortcut: 'βC' });
- });
-
- it('should trim whitespace from main and shortcut', () => {
- const result = parseContent(' Save ; βS ');
- expect(result).toEqual({ main: 'Save', shortcut: 'βS' });
- });
-
- it('should handle multiple separators by joining remaining parts', () => {
- const result = parseContent('Info; Part 1; Part 2');
- // Note: parts are trimmed individually, then joined with separator (no spaces added)
- expect(result).toEqual({ main: 'Info', shortcut: 'Part 1;Part 2' });
- });
-
- it('should handle empty content', () => {
- const result = parseContent('');
- expect(result).toEqual({ main: '' });
- });
-
- it('should handle content with only separator', () => {
- const result = parseContent(';');
- expect(result).toEqual({ main: '', shortcut: '' });
- });
- });
-
- describe('with custom separator', () => {
- it('should use custom separator for splitting', () => {
- const result = parseContent('Copy | βC', '|');
- expect(result).toEqual({ main: 'Copy', shortcut: 'βC' });
- });
-
- it('should handle multi-character separators', () => {
- const result = parseContent('Copy :: βC', '::');
- expect(result).toEqual({ main: 'Copy', shortcut: 'βC' });
- });
-
- it('should not split on default separator when custom is provided', () => {
- const result = parseContent('Save; βS', '|');
- expect(result).toEqual({ main: 'Save; βS' });
- });
- });
-});
+import { generateTooltipId } from '../parseDataAttributes';
describe('generateTooltipId', () => {
it('should generate unique IDs', () => {
diff --git a/src/utils/getTipProps.ts b/src/utils/getTipProps.ts
index 85ebb8c..a98c3f2 100644
--- a/src/utils/getTipProps.ts
+++ b/src/utils/getTipProps.ts
@@ -41,8 +41,8 @@ export interface TipPropsOptions {
moveTransitionDuration?: number;
/** Show or hide the arrow (default: true) */
showArrow?: boolean;
- /** Override content separator for keyboard shortcuts (default: ';') */
- contentSeparator?: string;
+ /** Keyboard shortcut to display alongside the tooltip content */
+ shortcut?: string;
/** Show tooltip when element receives keyboard focus */
showOnFocus?: boolean;
}
@@ -68,7 +68,7 @@ export interface TipPropsResult {
'data-tip-jump'?: '';
'data-tip-move-duration'?: string;
'data-tip-no-arrow'?: '';
- 'data-tip-separator'?: string;
+ 'data-tip-shortcut'?: string;
'data-tip-show-on-focus'?: '';
}
@@ -91,7 +91,8 @@ export interface TipPropsResult {
* ```tsx
* // With keyboard shortcut
* const tipProps = getTipProps({
- * tip: 'Copy; βC',
+ * tip: 'Copy',
+ * shortcut: 'βC',
* hideDelay: 500,
* });
*
@@ -165,8 +166,8 @@ export function getTipProps(options: TipPropsOptions): TipPropsResult {
result['data-tip-no-arrow'] = '';
}
- if (options.contentSeparator !== undefined) {
- result['data-tip-separator'] = options.contentSeparator;
+ if (options.shortcut !== undefined && options.shortcut !== '') {
+ result['data-tip-shortcut'] = options.shortcut;
}
if (options.showOnFocus) {
diff --git a/src/utils/parseDataAttributes.ts b/src/utils/parseDataAttributes.ts
index 16ae5c3..899e276 100644
--- a/src/utils/parseDataAttributes.ts
+++ b/src/utils/parseDataAttributes.ts
@@ -1,5 +1,5 @@
import { DEFAULT_OPTIONS } from '../constants';
-import type { ParsedTooltipData, Placement, TextBreak, TooltipTransitionBehavior } from '../types';
+import type { ParsedTooltipData, Placement, TextBreak } from '../types';
/**
* Valid placement values
@@ -50,7 +50,7 @@ function parseBooleanAttribute(value: string | undefined): boolean {
* Parse transition behavior from data attributes
* data-tip-move takes precedence over data-tip-jump
*/
-function parseTransitionBehavior(dataset: DOMStringMap): TooltipTransitionBehavior | undefined {
+function parseTransitionBehavior(dataset: DOMStringMap): 'move' | 'jump' | undefined {
if (dataset.tipMove !== undefined) {
return 'move';
}
@@ -107,40 +107,12 @@ export function parseDataAttributes(element: Element): ParsedTooltipData {
transitionBehavior: parseTransitionBehavior(dataset),
moveTransitionDuration: parseOptionalInt(dataset.tipMoveDuration),
showArrow: !parseBooleanAttribute(dataset.tipNoArrow),
- contentSeparator: dataset.tipSeparator,
+ shortcut: dataset.tipShortcut,
group: dataset.tipGroup,
showOnFocus: parseBooleanAttribute(dataset.tipShowOnFocus),
};
}
-/**
- * Parsed content with optional keyboard shortcut
- */
-export interface ParsedContent {
- main: string;
- shortcut?: string;
-}
-
-/**
- * Parse tooltip content to extract keyboard shortcut
- * Format: "Main text; keyboard shortcut"
- */
-export function parseContent(
- content: string,
- separator: string = DEFAULT_OPTIONS.contentSeparator
-): ParsedContent {
- const parts = content.split(separator).map((s) => s.trim());
-
- if (parts.length >= 2) {
- return {
- main: parts[0],
- shortcut: parts.slice(1).join(separator),
- };
- }
-
- return { main: content };
-}
-
/**
* Generate a unique ID for a tooltip target if not provided
*/
From d8a08f80776a12f80415372f41f7a3cea0c455e9 Mon Sep 17 00:00:00 2001
From: galangel
Date: Wed, 14 Jan 2026 14:43:23 +0200
Subject: [PATCH 2/2] Docs
Signed-off-by: galangel
---
src/KeyboardShortcuts.mdx | 136 +++++++++++++++++++++++++++-----------
src/Welcome.mdx | 65 ++++++++++--------
2 files changed, 136 insertions(+), 65 deletions(-)
diff --git a/src/KeyboardShortcuts.mdx b/src/KeyboardShortcuts.mdx
index cfef599..0a57644 100644
--- a/src/KeyboardShortcuts.mdx
+++ b/src/KeyboardShortcuts.mdx
@@ -31,6 +31,7 @@ Use `data-tip-shortcut` to add a keyboard shortcut badge:
```
The library displays:
+
1. The main text from `data-tip`
2. A styled `` badge with the shortcut
@@ -49,11 +50,15 @@ The library displays:
-
data-tip
+
+ data-tip
+
Tooltip content (main text)
-
data-tip-shortcut
+
+ data-tip-shortcut
+
Keyboard shortcut to display as a styled badge
@@ -77,10 +82,16 @@ The library displays:
-
enableShortcutStyle
+
+ enableShortcutStyle
+
boolean
-
true
-
Whether to render shortcuts with styled <kbd> badges
+
+ true
+
+
+ Whether to render shortcuts with styled <kbd> badges
+
@@ -106,8 +117,12 @@ The library displays:
-
.tip-magic-shortcut
-
Applied to the <kbd> element containing the shortcut
+
+ .tip-magic-shortcut
+
+
+ Applied to the <kbd> element containing the shortcut
+
@@ -124,13 +139,21 @@ The library displays:
-
--tip-magic-shortcut-bg
-
rgba(255, 255, 255, 0.15)
+
+ --tip-magic-shortcut-bg
+
+
+ rgba(255, 255, 255, 0.15)
+
Background color of the shortcut badge
-
--tip-magic-shortcut-text
-
#e5e7eb
+
+ --tip-magic-shortcut-text
+
+
+ #e5e7eb
+
Text color of the shortcut badge
@@ -190,44 +213,76 @@ Common keyboard symbols for use in shortcuts:
-
β
+
+ β
+
Command (Mac)
-
βC for copy
+
+ βC for copy
+
-
β₯
+
+ β₯
+
Option/Alt (Mac)
-
β₯Tab for switch
+
+ β₯Tab for switch
+
-
β§
+
+ β§
+
Shift
-
β§Enter for new line
+
+ β§Enter for new line
+
-
β
+
+ β
+
Control (Mac)
-
βSpace for autocomplete
+
+ βSpace for autocomplete
+
-
β΅
+
+ β΅
+
Enter/Return
-
β΅ to submit
+
+ β΅ to submit
+
-
β
+
+ β
+
Escape
-
β to cancel
+
+ β to cancel
+
-
β«
+
+ β«
+
Backspace/Delete
-
β« to delete
+
+ β« to delete
+
-
ββββ
+
+ ββββ
+
Arrow keys
-
ββ to navigate
+
+ ββ to navigate
+
@@ -256,9 +311,15 @@ Common keyboard symbols for use in shortcuts:
```tsx
```
@@ -282,8 +343,12 @@ Combine shortcuts with TipAdvisor for discoverability:
```tsx
- Copy
- Paste
+
+ Copy
+
+
+ Paste
+
{/* Press F1 to see all shortcuts */}
@@ -347,12 +412,7 @@ Keyboard shortcuts improve accessibility when implemented correctly:
Announce shortcuts to screen readers:
```tsx
-
+
Save
```
diff --git a/src/Welcome.mdx b/src/Welcome.mdx
index d0e2c3d..5a84b61 100644
--- a/src/Welcome.mdx
+++ b/src/Welcome.mdx
@@ -25,35 +25,41 @@ Whether you need simple tooltips, guided tours, or a way for users to discover k
Add data-tip="Hello" to any element. One tooltip instance moves gracefully between targets.
-
-
β¨οΈ Keyboard Shortcuts
-
Display shortcuts with data-tip-shortcut="βS". Styled badges that feel native to your UI.
-
+
+
β¨οΈ Keyboard Shortcuts
+
+ Display shortcuts with data-tip-shortcut="βS". Styled badges that feel native to
+ your UI.
+
+
-
-
πΊοΈ Guided Tours
-
Walk users through your interface step by step. Great for onboarding or introducing new features.
-
+
+
πΊοΈ Guided Tours
+
+ Walk users through your interface step by step. Great for onboarding or introducing new
+ features.
+
+
-
-
π Tip Advisor
-
A searchable menu of all shortcuts in your app. Press F1 to discover what's available.
-
+
+
π Tip Advisor
+
A searchable menu of all shortcuts in your app. Press F1 to discover what's available.
+
-
-
π¨ Smooth Transitions
-
Tooltips animate between elements. Group related items for cohesive movement.
-
+
+
π¨ Smooth Transitions
+
Tooltips animate between elements. Group related items for cohesive movement.
+
-
-
π± Accessible
-
Keyboard navigation, screen reader support, and respects reduced motion preferences.
-
+
+
π± Accessible
+
Keyboard navigation, screen reader support, and respects reduced motion preferences.
+
-
-
π Performant
-
Single global instance with event delegation. Minimal re-renders and memory footprint.
-
+
+
π Performant
+
Single global instance with event delegation. Minimal re-renders and memory footprint.
+
π Themeable
@@ -89,10 +95,14 @@ function App() {
### Add Tooltips
```tsx
-Save
+Save;
-{/* With keyboard shortcut */}
-Copy
+{
+ /* With keyboard shortcut */
+}
+
+ Copy
+;
```
---
@@ -107,6 +117,7 @@ React Tip Magic uses a global singleton pattern:
4. A single tooltip moves between targets
This means:
+
- Minimal DOM nodes
- Smooth transitions between tooltips
- Works with dynamically added elements