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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/blank-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"react": "catalog:react",
"react-dom": "catalog:react",
"react-intl": "catalog:react",
"@commercetools/nimbus": "workspace:*",
"@commercetools/nimbus-icons": "workspace:*"
},
Expand Down
Binary file added apps/docs/public/images/tooltip/tooltip.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/docs/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { DevOnly } from "./components/utils/dev-only.tsx";
import { DocumentMetaSettings } from "./components/document-meta-settings/document-meta-settings.tsx";
import { StickySidebar } from "./components/navigation/sticky-sidebar.tsx";
import { RouterProvider } from "./components/router";
import { IntlProvider } from "react-intl";
import { useAtom } from "jotai";
import { activeRouteAtom } from "./atoms/route";
import { IntlProvider } from "react-intl";

function App() {
const [, setActiveRoute] = useAtom(activeRouteAtom);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export const Code = (props: CodeProps) => {
const { className, children, ...rest } = props;

if (className) {
if (className.includes("-live-dev")) {
return <LiveCodeEditor {...props} defaultActiveTab="editor" />;
}

if (className.includes("-live")) {
return <LiveCodeEditor {...props} />;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ const removeImportStatements = (code: string) => {
type LiveCodeEditorProps = {
children?: ReactNode;
className?: string;
defaultActiveTab?: "preview" | "editor";
};

export const LiveCodeEditor = (props: LiveCodeEditorProps) => {
const [code, setCode] = useState(props.children);
const [activeTab, setActiveTab] = useState<"preview" | "editor">("preview");
const [activeTab, setActiveTab] = useState<"preview" | "editor">(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to introduce this change since we agreed that for the engineering guideline, the code editor should be the default view.

props.defaultActiveTab || "preview"
);

useEffect(() => {
setCode(props.children);
Expand Down
171 changes: 171 additions & 0 deletions packages/nimbus/src/components/tooltip/tooltip-dev.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
---
id: Components-tooltip-dev
title: Tooltip Engineering Guidelines
description: Engineering guideline for using the Nimbus Tooltip component
documentState: InitialDraft
order: 999
menu:
- Components
- Feedback
- TooltipDev
tags:
- component
- engineering
- migration
---

# Tooltip Component

Tooltips is a compound component that displays informative text when users hover over or focus on an element.

## Component Anatomy [Optional for simple components]

Component Anatomy here - image with parts and description as can be seen on [combobox](https://www.figma.com/design/AvtPX6g7OGGCRvNlatGOIY/NIMBUS-design-system?node-id=5311-13804&p=f&m=dev) for example

![](/images/tooltip/tooltip.png)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is likely not relevant to this component but since this is almost like a Poc, I added it. The idea is the component anatomy image might not be necessary for simple components so it is optional.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very valuable conceptually, especially for consumers who prefer to learn visually. I think it'd be worth including in every guideline


## Component Usage

### How to Import

```tsx
import { Tooltip } from '@commercetools/nimbus';
```

### Basic Example

A minimal, copy-and-paste example of the tooltip in its simplest form: edit the placement in code code interface to different positions of choice (top, left, right, etc)

```jsx-live-dev
// Example: A basic tooltip
const App = () => (
<Tooltip.Root placement="top">
<Button>Hover me</Button>
<Tooltip.Content
placement="top"
maxWidth="300px"
backgroundColor="blue"
color="white"
>
This is a helpful tooltip
</Tooltip.Content>
</Tooltip.Root>
)
```

### Common props and Variations

#### Placement Options
Control where the tooltip appears relative to its trigger: change the placement to any of these options to reposition the tooltip:
```"top" | "top left" | "top right" | "top start" | "top end" | "bottom" | "bottom left" | "bottom right" | "bottom start" | "bottom end" | "left" | "left top" | "left bottom" | "right" | "right top" | "right bottom" | "start" | "start top" | "start bottom" | "end" | "end top" | "end bottom"```

```jsx-live-dev
const App = () => (
<Tooltip.Root>
<Button>Tooltip placement</Button>
<Tooltip.Content placement="top">Tooltip on top</Tooltip.Content>
</Tooltip.Root>
)
```

#### Offset Options

Control the tooltip offset:
```offset```

```jsx-live-dev
const App = () => (
<Tooltip.Root>
<Button>Tooltip placement</Button>
<Tooltip.Content offset={50}>Tooltip on top</Tooltip.Content>
</Tooltip.Root>
)
```

```...Add example snippets for each relevant component prop```

## Testing Strategies and Best Practices

#### 1. Unit Testing with React Testing Library
> [!CAUTION]\
> **Note**
>
> - if you are using app-kit as your initial setup and for your entry point, then the provider is already available. You won't be needing the setup below..

**Recommended Testing Library Setup:**
```tsx
import { render, screen, userEvent } from '@testing-library/react';
import { NimbusProvider } from '@commercetools/nimbus';
import { IntlProvider } from 'react-intl';
import { Tooltip, Button } from '@commercetools/nimbus';

// Test wrapper with required providers
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<NimbusProvider>
{children}
</NimbusProvider>
</IntlProvider>
);
Comment on lines +102 to +109
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't we going to put NimbusProvider into App Kit? One of Tobi's biggest complaints was this here pattern in tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are doing that, then that would be great!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any examples? Do the design guidelines suffice?

Yes, I think we can point to the guidelines here


// Custom render function
const renderWithProviders = (ui: React.ReactElement) =>
render(ui, { wrapper: TestWrapper });
```

#### 2. Storybook Integration for Testing

Use the [Nimbus storybook Tooltip component story](https://nimbus-storybook-6l4m4g78g-commercetools.vercel.app/?path=/docs/components-tooltip-tooltip--docs) as a playground for visual testing of the component.

#### 3. Regression tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything in here?

Copy link
Contributor Author

@ddouglasz ddouglasz Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its kind of a placeholder for now since we have not fully come up with a regression test strategy yet.


### 4. Accessibility requirements

Nimbus Tooltip automatically handles most accessibility requirements, but developers should be aware of these basic requirements:
- Add `aria-label` to enable proper definition of the alert component.
- Ensure `aria-describedby` is present for error messages.
- Include `role="alert"` for critical notifications.

## Quick Reference Summary

### Design Guidelines
For essential patterns and guidelines, see the [Tooltip design guideline]((https://Nimbus-docs/components/feedback/tooltip))

### Code Patterns

#### Hooks
```tsx
// Custom Hook Pattern
const useTooltip = (content: string, options = {}) => { ...};

// Using the Custom Hook
const MyComponent = () => {
const { tooltipProps, contentProps } = useTooltip(
"This action will save your changes",
{ placement: "bottom", delay: 300 }
);

return (
<Tooltip.Root {...tooltipProps}>
<Button>Save</Button>
<Tooltip.Content {...contentProps} />
</Tooltip.Root>
);
};
```


### API Reference

<PropsTable id="Tooltip" />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need a different topic to discuss how we would manually add props. The exposed props listing on the props table does not capture a lot of the important props.


<br/>

### Need Help?
Join `#nimbus-migration` on Slack or check the [Nimbus Storybook](https://nimbus.commercetools.com/?path=/story/components-tooltip) for live examples.

**Additional Resources:**
- [Tooltip design guideline](https://Nimbus-docs/components/feedback/tooltip)
- [React-Aria Tooltip Docs](https://react-spectrum.adobe.com/react-aria/Tooltip.html#tooltip)
- [ARIA Tooltip Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/)
- [Figma Design Library](https://www.figma.com/design/gHbAJGfcrCv7f2bgzUQgHq/NIMBUS-Guidelines?node-id=1695-45519&m)
176 changes: 176 additions & 0 deletions packages/nimbus/src/components/tooltip/tooltip-migration-guide.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
---
id: Components-tooltip-migration
title: Tooltip Migration Guideline
description: Engineering guideline for migrating from UI Kit to Nimbus Tooltip component
documentState: InitialDraft
order: 999
menu:
- Components
- Feedback
- TooltipMigration
tags:
- component
- engineering
- migration
---

# Tooltip Migration Guideline (Uikit to Nimbus)

## Basic requirement

Migration requirements like ensuring the right codemod exists, where to find it or put it etc.


### Code Mod Instructions

If a code mod exists for Tooltip migration, run:

```bash
npx nimbus-codemods migrate-tooltip
```

### Manual Migration Steps

For manual migration, follow these steps:

#### Step 1: Replace Component Structure
**Before (UI Kit):**
```tsx
<Tooltip title="Helpful information" isOpen={isOpen} onClose={handleClose}>
<Button>Hover me</Button>
</Tooltip>
```

**After (Nimbus):**
```tsx
<Tooltip.Root isOpen={isOpen} onOpenChange={setIsOpen}>
<Button>Hover me</Button>
<Tooltip.Content>Helpful information</Tooltip.Content>
</Tooltip.Root>
```

#### Step 2: Update Event Handlers
**Before:**
```tsx
const handleClose = () => {
setIsOpen(false);
};
```

**After:**
```tsx
const handleOpenChange = (isOpen: boolean) => {
setIsOpen(isOpen);
};
```

#### Step 3: Handle Removed Props
For `horizontalConstraint`, wrap with a container or use CSS:

**Before:**
```tsx
<Tooltip title="Long text content..." horizontalConstraint="m">
<Button>Button</Button>
</Tooltip>
```

**After:**
```tsx
<Tooltip.Root>
<Button>Button</Button>
<Tooltip.Content maxWidth: '200px'>
Long text content...
</Tooltip.Content>
</Tooltip.Root>
```

### API Changes and Component Mapping

These are the possible changes for this component after a successful migration to Nimbus

| UI Kit Component | Nimbus Component | Notes |
|------------------|------------------|-------|
| `<Tooltip>` | `<Tooltip.Root>` + `<Tooltip.Content>` | Now uses compound component pattern |
| `title` prop | `<Tooltip.Content>` children | Text content now goes as children |
| `isOpen` prop | `isOpen` prop | ✅ Same API |
| `onClose` | `onOpenChange` | ⚠️ Renamed, now receives boolean parameter |
| `horizontalConstraint` | Not supported | ❌ Removed - use CSS or container sizing |
Comment on lines +91 to +97
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful. I cried. 10/10


#### Props Renamed or Changed
- `onClose` → `onOpenChange`: Now receives `(isOpen: boolean) => void` instead of `() => void`
- `title` prop → Content as children: Text now goes inside `<Tooltip.Content>`
- `delay` and `closeDelay`: More granular control over timing

#### Props Removed
- `horizontalConstraint`: Use CSS width constraints on content instead
- `tone`: Styling is now handled through design tokens

#### New Props Introduced
- `placement`: More placement options with modifiers
- `defaultOpen`: Set initial state without controlled component


## Testing Strategies and Best Practices After Migration

#### 1. Unit Testing with React Testing Library

List all possible test adjustments to be made to unit tests, regression tests etc

**Recommended Testing Library Setup:**
```tsx
import { render, screen, userEvent } from '@testing-library/react';
import { NimbusProvider } from '@commercetools/nimbus';
import { IntlProvider } from 'react-intl';
import { Tooltip, Button } from '@commercetools/nimbus';

// Test wrapper with required providers
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
<IntlProvider locale="en" messages={{}} defaultLocale="en">
<NimbusProvider>
{children}
</NimbusProvider>
</IntlProvider>
);

// Custom render function
const renderWithProviders = (ui: React.ReactElement) =>
render(ui, { wrapper: TestWrapper });
```

## ⚠️ Caveats and Best Practices For Migration

### Testing Requirements
- **Run Tests After Migration**: Always run your test suites after migrating to ensure components function as expected
- **Update Test Patterns**: Switch from UI Kit testing patterns to Nimbus patterns shown in this guide
- **Provider Requirements**: Ensure tests include both `NimbusProvider` and `IntlProvider`

### Common Pitfalls
- **Event Handler Updates**: Remember `onClose` is now `onOpenChange` with different signature
- **Compound Component Pattern**: Both `Tooltip.Root` and `Tooltip.Content` are required


**Additional Resources:**
- [React-Aria Tooltip Docs](https://react-spectrum.adobe.com/react-aria/Tooltip.html#tooltip)
- [ARIA Tooltip Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/)
- [Figma Design Library](https://www.figma.com/design/gHbAJGfcrCv7f2bgzUQgHq/NIMBUS-Guidelines?node-id=1695-45519&m)

### API Reference

<PropsTable id="Tooltip" />

## Quick Reference Summary

### Essential Patterns
- **Compound Components**: Always use `<Tooltip.Root>` + `<Tooltip.Content>`
- **Testing**: Include both providers in test wrapper
- **Accessibility**: Built-in ARIA support, keyboard navigation included

### Migration Checklist
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should calm some nerves - it may not ever be totally complete for every instance but that's a lovely idea.

- [ ] Replace UI Kit `<Tooltip title="...">` with `<Tooltip.Root>` + `<Tooltip.Content>`
- [ ] Update `onClose` handlers to `onOpenChange`
- [ ] Remove `horizontalConstraint`.
- [ ] Test hover, focus, and keyboard interactions
- [ ] Verify accessibility features

### Need Help?
Join `#nimbus-migration` on Slack or check the [Nimbus Storybook](https://nimbus.commercetools.com/?path=/story/components-tooltip) for live examples.
Loading