Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 8 additions & 3 deletions src/lib/components/iconhelper/IconHelper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CSSProperties, ReactNode } from 'react';
import styled from 'styled-components';
import { Icon } from '../icon/Icon.component';
import { fontSize } from '../../style/theme';
import { Position, Tooltip } from '../tooltip/Tooltip.component';

type IconHelpProps = {
Expand All @@ -19,14 +20,18 @@ type IconHelpProps = {

const HelpButton = styled.button`
display: inline-flex;
align-items: center;
justify-content: center;
width: ${fontSize.base};
height: ${fontSize.base};
background: none;
border: none;
padding: 0;
margin: 0;
color: inherit;
font: inherit; /* Inherit font sizing */
vertical-align: text-bottom; /* Align with text */
line-height: 1;
font-size: ${fontSize.base};
line-height: 0;
vertical-align: -0.125em;
cursor: default;
&:focus-visible {
outline: 2px dashed ${(props) => props.theme.selectedActive};
Expand Down
53 changes: 53 additions & 0 deletions stories/IconHelp/iconhelp.guideline.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Meta, Canvas } from '@storybook/addon-docs/blocks';
import * as Stories from './iconhelp.stories';

<Meta name="Guideline" of={Stories} />

# IconHelp

`IconHelp` is a small info icon that reveals a tooltip on hover or focus. Use it to provide secondary information without overloading the interface.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The intro should be a single descriptive sentence, per the guideline writing convention in CLAUDE.md. The second sentence ("Use it to provide secondary information without overloading the interface") is a usage recommendation and belongs in "When to use it".

Suggested intro: `IconHelp` is an inline icon button that reveals a tooltip on hover or focus.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied as suggested in e009220. Intro is now IconHelp` is an inline icon button that reveals a tooltip on hover or focus. and the usage recommendation moved into the opening line of "When to use it".


<Canvas of={Stories.Simple} sourceState="none" />

## When to use it

Reach for `IconHelp` whenever a user might have to **guess** what a label, value or concept means. If the wording is not self-explanatory and there is no room — or no need — to spell it out in the layout, an `IconHelp` should be considered.

Typical cases:

- A label or a settings name that uses a domain term (`Object lock`, `Replication policy`, `RPO`).
- A value whose meaning is not transparent (`Governance`, `Compliance`, `Standard` storage class).
- A column header, badge, or status that benefits from a one-line definition.

## When not to use it

- **Don't use it as the only way to convey critical information.** A tooltip is hidden by default — anything required to complete a task must remain visible (helper text, error message, inline description).
- **Don't stack multiple `IconHelp` next to each other.** If a section needs that much explanation, write a short paragraph or link to documentation instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two parts of this bullet need clarification.

"Write a short paragraph" — where? Name the concrete pattern: a description paragraph below the section heading, an InfoMessage above the content area, etc. Without a target location, this advice is not actionable.

"Link to documentation" has the same gap: how is the link surfaced? Inline in the layout? As an InfoMessage footer? This needs a concrete pattern or should be removed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Rewrote the bullet in e009220 to name concrete patterns: a description paragraph under the section heading, or an InfoMessage above the content area (with a link to the dedicated doc page when a longer explanation is needed). "Never long text in a tooltip" is now explicit.

- **Don't replace good labels with an `IconHelp`.** Fix the label first; add help only when the term itself cannot be made clearer.
- **Don't put long content inside the tooltip.** Keep it to one or two short sentences. For longer explanations, use a dedicated documentation page.

## Explaining values in a key/value list

In summary or detail pages, attach an `IconHelp` only to the values whose meaning is not obvious. Leave plain values (a name, an email, an ID) without one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IconHelp can also go on the key side when the label itself is the opaque term: "Object lock", "RPO", "Standard" (as a storage class). The current phrasing ("attach... only to the values") is too restrictive and will mislead consumers.

Suggest updating this sentence and adding a labelHelp prop to KeyValueRow in the story to show key-side placement alongside value-side placement.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in e009220. The section now spells out both sides — key-side when the label itself is opaque (Object lock, RPO, Replication policy) and value-side when the value is the unclear term. The story's KeyValueRow got a new labelHelp prop and Object lock + RPO are demonstrated key-side.


<Canvas of={Stories.InKeyValueList} sourceState="none" />

## In a form

In forms, `FormGroup` already exposes a `labelHelpTooltip` prop that renders an `IconHelp` next to the field label — you don't need to add one manually. Use it to clarify:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The section is missing the key decision rule: use helper text when the information is needed to complete the task, because it is always visible. IconHelp in a form is appropriate only for conceptual context — what a field represents, why it matters — not for constraints the user must act on to fill the form correctly.

Without this distinction, consumers have no clear signal for when to reach for IconHelp versus FormGroup.helperText.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added in e009220. "In a form" now opens with the decision rule: helper text (via FormGroup's help prop, always visible) for task-completion info; IconHelp (labelHelpTooltip) only for conceptual context (what the field represents, why it matters).


- **What the field represents** — e.g. _"The name other users will see on your profile."_
- **The accepted format or validation rule** — e.g. _"Must be 3–32 characters, letters and digits only."_

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remove this bullet. Format and validation rules ("Must be 3–32 characters, letters and digits only") are needed to complete the task, so they belong in always-visible helper text, not a tooltip. This contradicts the rule two bullets above: "don't use IconHelp as the only way to convey critical information".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Even if we are not super strict in the UI, it should be strict in the design system.

- **The impact of the value** — e.g. _"Enabling versioning keeps a copy of every overwrite or delete."_

## Placement

The tooltip defaults to `right`. Pick another placement only when `right` is clipped by the container or overlaps important content.

<Canvas of={Stories.WithPlacement} sourceState="none" />

## Accessibility

- Always pass an `aria-label` that describes _what the tooltip explains_, not just `"Help"`. Example: `aria-label="More info about replication policy"`.
- The icon renders as a real `<button>`, so it is reachable by keyboard (`Tab`) and the tooltip opens on focus.
- The tooltip content is the visible text — keep it informative and complete on its own.
125 changes: 125 additions & 0 deletions stories/IconHelp/iconhelp.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React from 'react';
import { IconHelp } from '../../src/lib/components/iconhelper/IconHelper';
import { Text } from '../../src/lib/components/text/Text.component';
import { Stack } from '../../src/lib/spacing';
import { Wrapper } from '../common';

export default {
title: 'Components/IconHelp',
component: IconHelp,
decorators: [(story: () => React.ReactNode) => <Wrapper>{story()}</Wrapper>],
};

export const Simple = {
name: 'Simple',
render: () => (
<Text>
Replication policy{' '}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The {' '} before <IconHelp> is the correct spacing convention — but it is not documented anywhere. Add a note in the guideline: one space between the adjacent text and the icon, managed by the parent, not built into the component.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is debatable, I don't know if it's a spacing component or if it's managed with a space.

<IconHelp
tooltipMessage="Defines how objects are replicated across locations."
aria-label="More info about replication policy"
/>
</Text>
),
};

export const WithPlacement = {
name: 'Tooltip placement',
render: () => (
<Stack direction="vertical" gap="r24">
<Text>
Placement <b>top</b>{' '}
<IconHelp
placement="top"
tooltipMessage="Appears above the icon."
aria-label="Top tooltip"
/>
</Text>
<Text>
Placement <b>right</b> (default){' '}
<IconHelp
placement="right"
tooltipMessage="Appears to the right of the icon."
aria-label="Right tooltip"
/>
</Text>
<Text>
Placement <b>bottom</b>{' '}
<IconHelp
placement="bottom"
tooltipMessage="Appears below the icon."
aria-label="Bottom tooltip"
/>
</Text>
<Text>
Placement <b>left</b>{' '}
<IconHelp
placement="left"
tooltipMessage="Appears to the left of the icon."
aria-label="Left tooltip"
/>
</Text>
</Stack>
),
};

const KeyValueRow = ({
label,
value,
helpMessage,
helpAriaLabel,
}: {
label: string;
value: React.ReactNode;
helpMessage?: React.ReactNode;
helpAriaLabel?: string;
}) => (
<div
style={{
display: 'grid',
gridTemplateColumns: '200px 1fr',
alignItems: 'baseline',
gap: '16px',
padding: '6px 0',
}}
>
<Text color="textSecondary">{label}</Text>
<Text>
{value}
{helpMessage && (
<>
{' '}
<IconHelp
tooltipMessage={helpMessage}
aria-label={helpAriaLabel ?? `More info about ${label}`}
/>
</>
)}
</Text>
</div>
);

export const InKeyValueList = {
name: 'Explaining values',
render: () => (
<div style={{ maxWidth: 560 }}>
<KeyValueRow label="Bucket name" value="prod-customer-assets" />
<KeyValueRow
label="Versioning"
value="Enabled"
helpMessage="Once enabled, every overwrite or delete keeps the previous version."
/>
<KeyValueRow
label="Object lock"
value="Governance"
helpMessage="Governance mode allows privileged users to change retention; Compliance mode does not."
/>
<KeyValueRow
label="Storage class"
value="Standard"
helpMessage="Affects durability, latency and pricing of stored objects."
/>
<KeyValueRow label="Region" value="us-east-1" />
</div>
),
};
Loading