Hide unused helper marks on selection cards and modal section labels until tooltip behavior ships.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
adilallo
2026-08-21 16:28:38 -06:00
co-authored by Cursor
parent ef60175805
commit 440b6a9657
19 changed files with 92 additions and 25 deletions
@@ -70,7 +70,7 @@ function ApplicableScopeFieldComponent({
return ( return (
<div className={`flex flex-col gap-2 ${className}`.trim()}> <div className={`flex flex-col gap-2 ${className}`.trim()}>
<InputLabel label={label} helpIcon size="s" palette="default" /> <InputLabel label={label} size="s" palette="default" />
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
{scopes.map((scope) => { {scopes.map((scope) => {
const isSelected = selectedScopes.includes(scope); const isSelected = selectedScopes.includes(scope);
@@ -60,7 +60,6 @@ function CustomMethodCardFieldBlocksSummaryViewComponent({
<div key={block.id} className="flex flex-col gap-2"> <div key={block.id} className="flex flex-col gap-2">
<InputLabel <InputLabel
label={block.blockTitle} label={block.blockTitle}
helpIcon
size="s" size="s"
palette="default" palette="default"
/> />
@@ -123,7 +122,6 @@ function CustomMethodCardFieldBlocksSummaryViewComponent({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<InputLabel <InputLabel
label={block.blockTitle} label={block.blockTitle}
helpIcon
size="s" size="s"
palette="default" palette="default"
/> />
@@ -169,6 +167,7 @@ function CustomMethodCardFieldBlocksSummaryViewComponent({
<IncrementerBlock <IncrementerBlock
key={block.id} key={block.id}
label={block.blockTitle} label={block.blockTitle}
helpIcon={false}
value={block.defaultPercent} value={block.defaultPercent}
min={1} min={1}
max={100} max={100}
@@ -30,7 +30,6 @@ function CustomMethodCardUploadBlockRowViewComponent({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<InputLabel <InputLabel
label={block.blockTitle} label={block.blockTitle}
helpIcon
size="s" size="s"
palette="default" palette="default"
/> />
@@ -77,6 +76,7 @@ function CustomMethodCardUploadBlockRowViewComponent({
) : ( ) : (
<Upload <Upload
active={!busy} active={!busy}
showHelpIcon={false}
hintText={busy ? uploadingHint : uploadHint} hintText={busy ? uploadingHint : uploadHint}
onClick={onUploadClick} onClick={onUploadClick}
/> />
@@ -51,12 +51,10 @@ function CustomMethodCardWizardFieldBodiesViewComponent({
value={textBlockTitle} value={textBlockTitle}
onChange={onTextBlockTitleChange} onChange={onTextBlockTitleChange}
maxLength={CUSTOM_METHOD_CARD_WIZARD_MAX_FIELD_CHARS} maxLength={CUSTOM_METHOD_CARD_WIZARD_MAX_FIELD_CHARS}
showHelpIcon
/> />
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<InputLabel <InputLabel
label={copy.text.placeholderLabel} label={copy.text.placeholderLabel}
helpIcon
size="s" size="s"
palette="default" palette="default"
/> />
@@ -82,7 +80,6 @@ function CustomMethodCardWizardFieldBodiesViewComponent({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<InputLabel <InputLabel
label={copy.badges.blockTitleLabel} label={copy.badges.blockTitleLabel}
helpIcon
helperText={copy.requiredHint} helperText={copy.requiredHint}
size="s" size="s"
palette="default" palette="default"
@@ -127,7 +124,6 @@ function CustomMethodCardWizardFieldBodiesViewComponent({
value={uploadBlockTitle} value={uploadBlockTitle}
onChange={onUploadBlockTitleChange} onChange={onUploadBlockTitleChange}
maxLength={CUSTOM_METHOD_CARD_WIZARD_MAX_FIELD_CHARS} maxLength={CUSTOM_METHOD_CARD_WIZARD_MAX_FIELD_CHARS}
showHelpIcon
/> />
{hasUploadPreview ? ( {hasUploadPreview ? (
<div className="relative inline-block max-w-full"> <div className="relative inline-block max-w-full">
@@ -158,6 +154,7 @@ function CustomMethodCardWizardFieldBodiesViewComponent({
) : ( ) : (
<Upload <Upload
active={!uploadPersisting} active={!uploadPersisting}
showHelpIcon={false}
hintText={ hintText={
uploadPersisting && uploadBusyHint uploadPersisting && uploadBusyHint
? uploadBusyHint ? uploadBusyHint
@@ -188,10 +185,10 @@ function CustomMethodCardWizardFieldBodiesViewComponent({
value={proportionBlockTitle} value={proportionBlockTitle}
onChange={onProportionBlockTitleChange} onChange={onProportionBlockTitleChange}
maxLength={CUSTOM_METHOD_CARD_WIZARD_MAX_FIELD_CHARS} maxLength={CUSTOM_METHOD_CARD_WIZARD_MAX_FIELD_CHARS}
showHelpIcon
/> />
<IncrementerBlock <IncrementerBlock
label={copy.proportion.defaultLabel} label={copy.proportion.defaultLabel}
helpIcon={false}
value={proportionDefault} value={proportionDefault}
min={1} min={1}
max={100} max={100}
@@ -38,6 +38,7 @@ export default function MethodCardCustomizeModalHeader({
value={titleValue} value={titleValue}
onChange={(e) => onTitleChange(e.target.value)} onChange={(e) => onTitleChange(e.target.value)}
inputSize="medium" inputSize="medium"
showHelpIcon={false}
/> />
{showDescription ? ( {showDescription ? (
<ModalTextAreaField <ModalTextAreaField
@@ -2,8 +2,9 @@
/** /**
* Shared "labelled text area" field used by every create flow modal section. * Shared "labelled text area" field used by every create flow modal section.
* Pairs an `InputLabel` (with help icon) with a `TextArea` set to the embedded * Pairs an `InputLabel` with a `TextArea` set to the embedded appearance —
* appearance — matching the Figma "Control / Text Area" pattern. * matching the Figma "Control / Text Area" pattern. Section help marks stay
* off until tooltip behavior ships.
*/ */
import { memo, useId } from "react"; import { memo, useId } from "react";
@@ -13,7 +14,7 @@ import InputLabel from "../../../components/type/InputLabel";
export interface ModalTextAreaFieldProps { export interface ModalTextAreaFieldProps {
/** Label rendered above the text area. */ /** Label rendered above the text area. */
label: string; label: string;
/** Show the help "?" icon next to the label (default `true`). */ /** Show the help "?" icon next to the label. Off until tooltip behavior ships. */
helpIcon?: boolean; helpIcon?: boolean;
/** Current text value. */ /** Current text value. */
value: string; value: string;
@@ -30,7 +31,7 @@ export interface ModalTextAreaFieldProps {
function ModalTextAreaFieldComponent({ function ModalTextAreaFieldComponent({
label, label,
helpIcon = true, helpIcon = false,
value, value,
onChange, onChange,
rows = 4, rows = 4,
@@ -76,6 +76,7 @@ function DecisionApproachEditFieldsComponent({
/> />
<IncrementerBlock <IncrementerBlock
label={t.sectionHeadings.consensusLevel} label={t.sectionHeadings.consensusLevel}
helpIcon={false}
value={value.consensusLevel} value={value.consensusLevel}
min={CONSENSUS_LEVEL_MIN} min={CONSENSUS_LEVEL_MIN}
max={CONSENSUS_LEVEL_MAX} max={CONSENSUS_LEVEL_MAX}
@@ -148,7 +148,7 @@ export function CardStackView({
recommended={item.recommended ?? false} recommended={item.recommended ?? false}
selected={isSelected(item.id)} selected={isSelected(item.id)}
orientation="vertical" orientation="vertical"
showInfoIcon={true} showInfoIcon={false}
onClick={() => onCardSelect(item.id)} onClick={() => onCardSelect(item.id)}
/> />
))} ))}
@@ -457,7 +457,7 @@ export function CardStackView({
recommended={item.recommended ?? false} recommended={item.recommended ?? false}
selected={isSelected(item.id)} selected={isSelected(item.id)}
orientation="vertical" orientation="vertical"
showInfoIcon={true} showInfoIcon={false}
onClick={() => onCardSelect(item.id)} onClick={() => onCardSelect(item.id)}
/> />
))} ))}
@@ -6,7 +6,7 @@ import type { SelectionProps } from "./Selection.types";
/** /**
* Figma: "Card / CardSelection" — stacked tile e.g. `16775:28762` (recommended + label + supportText). * Figma: "Card / CardSelection" — stacked tile e.g. `16775:28762` (recommended + label + supportText).
* `orientation="horizontal"` selects that vertical stack; `"vertical"` is label + optional info icon with tag on the right (CardStack `singleStack` / right-rail). * `orientation="horizontal"` selects that vertical stack; `"vertical"` is label + tag on the right (CardStack `singleStack` / right-rail). Helper `?` stays off until tooltip behavior ships.
*/ */
const SelectionContainer = memo<SelectionProps>( const SelectionContainer = memo<SelectionProps>(
({ ({
@@ -4,6 +4,7 @@ export interface SelectionProps {
recommended?: boolean; recommended?: boolean;
selected?: boolean; selected?: boolean;
orientation: "horizontal" | "vertical"; orientation: "horizontal" | "vertical";
/** Off in product until tooltip behavior ships. */
showInfoIcon?: boolean; showInfoIcon?: boolean;
/** Optional id for the root (e.g. `data-card-id` for focus after modal close). */ /** Optional id for the root (e.g. `data-card-id` for focus after modal close). */
id?: string; id?: string;
@@ -291,7 +291,7 @@ function ReadOnlyScopeField({
}) { }) {
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<InputLabel label={label} helpIcon size="s" palette="default" /> <InputLabel label={label} size="s" palette="default" />
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
{scopes.map((scope) => ( {scopes.map((scope) => (
<Chip <Chip
@@ -317,7 +317,7 @@ function ReadOnlyValueField({
}) { }) {
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<InputLabel label={label} helpIcon size="s" palette="default" /> <InputLabel label={label} size="s" palette="default" />
<span className="text-medium-label text-[color:var(--color-content-default-primary)]"> <span className="text-medium-label text-[color:var(--color-content-default-primary)]">
{value} {value}
</span> </span>
+6 -5
View File
@@ -36,7 +36,8 @@ export default {
}, },
showInfoIcon: { showInfoIcon: {
control: { type: "boolean" }, control: { type: "boolean" },
description: "Show info icon next to label (typically in vertical)", description:
"Optional helper ? next to the label. Off in product until tooltip behavior ships.",
}, },
onClick: { action: "clicked" }, onClick: { action: "clicked" },
}, },
@@ -82,7 +83,7 @@ export const VerticalRecommended = {
recommended: true, recommended: true,
selected: false, selected: false,
orientation: "vertical", orientation: "vertical",
showInfoIcon: true, showInfoIcon: false,
}, },
}; };
@@ -93,7 +94,7 @@ export const VerticalSelected = {
recommended: false, recommended: false,
selected: true, selected: true,
orientation: "vertical", orientation: "vertical",
showInfoIcon: true, showInfoIcon: false,
}, },
}; };
@@ -134,7 +135,7 @@ export const AllVariants = {
recommended={true} recommended={true}
selected={false} selected={false}
orientation="vertical" orientation="vertical"
showInfoIcon={true} showInfoIcon={false}
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
@@ -147,7 +148,7 @@ export const AllVariants = {
recommended={false} recommended={false}
selected={true} selected={true}
orientation="vertical" orientation="vertical"
showInfoIcon={true} showInfoIcon={false}
/> />
</div> </div>
</div> </div>
@@ -9,7 +9,7 @@ export default {
docs: { docs: {
description: { description: {
component: component:
"Shared 'labelled text area' field used by every create-flow modal section. Pairs `InputLabel` (with help icon) with a `TextArea` set to the `embedded` appearance.", "Shared 'labelled text area' field used by every create-flow modal section. Pairs `InputLabel` with a `TextArea` set to the `embedded` appearance. Section help marks stay off until tooltip behavior ships.",
}, },
}, },
}, },
@@ -35,7 +35,7 @@ export const Default = {
}, },
args: { args: {
label: "Description", label: "Description",
helpIcon: true, helpIcon: false,
placeholder: "What does this rule cover?", placeholder: "What does this rule cover?",
rows: 4, rows: 4,
}, },
@@ -119,4 +119,10 @@ describe("ApplicableScopeField behavior", () => {
screen.queryByRole("textbox", { name: /Add Applicable Scope/i }), screen.queryByRole("textbox", { name: /Add Applicable Scope/i }),
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
}); });
it("does not render a section help icon", () => {
renderWithProviders(<ApplicableScopeField {...baseProps} />);
expect(screen.queryByAltText("Help")).not.toBeInTheDocument();
});
}); });
@@ -77,6 +77,7 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
); );
const dialog = await screen.findByRole("dialog"); const dialog = await screen.findByRole("dialog");
expect(within(dialog).queryByAltText("Help")).not.toBeInTheDocument();
const textboxes = within(dialog).getAllByRole("textbox"); const textboxes = within(dialog).getAllByRole("textbox");
expect(textboxes.length).toBe(3); expect(textboxes.length).toBe(3);
const corePrincipleField = textboxes[0] as HTMLTextAreaElement; const corePrincipleField = textboxes[0] as HTMLTextAreaElement;
@@ -45,6 +45,7 @@ describe("CustomMethodCardFieldBlocksSummary", () => {
}), }),
).toBeInTheDocument(); ).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Upload" })).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Upload" })).not.toBeInTheDocument();
expect(screen.queryByAltText("Help")).not.toBeInTheDocument();
}); });
it("after remove, parent can pass cleared blocks and Upload shows again", () => { it("after remove, parent can pass cleared blocks and Upload shows again", () => {
@@ -85,4 +85,29 @@ describe("ModalTextAreaField behavior", () => {
expect(screen.getByRole("textbox", { name: /Notes/i })).toBeDisabled(); expect(screen.getByRole("textbox", { name: /Notes/i })).toBeDisabled();
}); });
it("does not render a section help icon by default", () => {
renderWithProviders(
<ModalTextAreaField
label="Core principle"
value=""
onChange={() => {}}
/>,
);
expect(screen.queryByAltText("Help")).not.toBeInTheDocument();
});
it("renders a section help icon when helpIcon is true", () => {
renderWithProviders(
<ModalTextAreaField
label="Core principle"
helpIcon
value=""
onChange={() => {}}
/>,
);
expect(screen.getByAltText("Help")).toBeInTheDocument();
});
}); });
+15
View File
@@ -65,6 +65,21 @@ describe("CardStack Component", () => {
expect(screen.getAllByText("Option C").length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText("Option C").length).toBeGreaterThanOrEqual(1);
}); });
test("hides helper icons on compact, expanded, and single-stack cards", () => {
const { unmount } = render(
<CardStack cards={SAMPLE_CARDS} expanded={false} />,
);
expect(screen.queryByText("?")).not.toBeInTheDocument();
unmount();
const expanded = render(<CardStack cards={SAMPLE_CARDS} expanded={true} />);
expect(screen.queryByText("?")).not.toBeInTheDocument();
expanded.unmount();
render(<CardStack cards={SAMPLE_CARDS} layout="singleStack" />);
expect(screen.queryByText("?")).not.toBeInTheDocument();
});
test("expanded tiles use the compact 142px CardSelection height", () => { test("expanded tiles use the compact 142px CardSelection height", () => {
render(<CardStack cards={SAMPLE_CARDS} expanded={true} />); render(<CardStack cards={SAMPLE_CARDS} expanded={true} />);
+18
View File
@@ -54,6 +54,24 @@ describe("Selection Component", () => {
expect(card).toHaveClass("flex-row"); expect(card).toHaveClass("flex-row");
}); });
it("does not render a helper icon by default", () => {
render(<Selection {...defaultProps} orientation="vertical" />);
expect(screen.queryByText("?")).not.toBeInTheDocument();
});
it("renders a helper icon when showInfoIcon is true", () => {
render(
<Selection
{...defaultProps}
orientation="vertical"
showInfoIcon={true}
/>,
);
expect(screen.getByText("?")).toBeInTheDocument();
});
it("handles click events", () => { it("handles click events", () => {
const handleClick = vi.fn(); const handleClick = vi.fn();
render(<Selection {...defaultProps} onClick={handleClick} />); render(<Selection {...defaultProps} onClick={handleClick} />);