Skip to content
Merged
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
25 changes: 25 additions & 0 deletions .changeset/fieldstatus-tooltip-deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'@astryxdesign/core': patch
---

[feat] FieldStatus: the `tooltip` variant is deprecated and is removed in the next minor release. FieldStatus has no tooltip presentation: a direct `<FieldStatus variant="tooltip">` draws a plain message box with no tooltip, and your editor now marks it deprecated. To show a status message from a field's on-field icon, set `statusVariant="tooltip"` on the field control instead; that stays supported.

Before:

```tsx
<FieldStatus type="error" message="Required" variant="tooltip" />
```

After:

```tsx
<TextInput
label="Name"
value={name}
onChange={setName}
status={{type: 'error', message: 'Required'}}
statusVariant="tooltip"
/>
Comment on lines +16 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

TextInputProps.value is required, so this example fails with TS2741 as written. Add value and onChange.

```

@cixzhang
7 changes: 7 additions & 0 deletions .changeset/richtext-status-placement-tooltip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/richtext': patch
---

[chore] RichTextEditor: `statusVariant` names `'tooltip'` in its own type, so it keeps accepting `statusVariant="tooltip"` after FieldStatus drops its deprecated `tooltip` variant in the next minor release. No behavior change.

@cixzhang
5 changes: 3 additions & 2 deletions packages/core/src/ComplexSelector/ComplexSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {useDevWarning} from '../hooks/useDevWarning';
import {mergeRefs} from '../utils/mergeRefs';
import {useMergedRefs} from '../hooks/useMergedRefs';
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
import {Field, inputWrapperStyles, type FieldStatusVariant} from '../Field';
import {Field, inputWrapperStyles} from '../Field';
import {Icon, renderIconSlot, type IconType} from '../Icon';
import {Spinner} from '../Spinner';
import {useTranslator} from '../i18n';
Expand Down Expand Up @@ -65,6 +65,7 @@ import {usePressFeedback} from '../hooks/usePressFeedback';
import type {SizeValue} from '../utils/types';
import {themeProps} from '../utils/themeProps';
import {layerViewportInset} from '../Layer/layerViewportInset.stylex';
import type {FieldStatusPlacement} from '../Field/fieldStatusPlacement';

// The popup content's own block-size cap; the viewport cap is the layer
// runtime's.
Expand Down Expand Up @@ -417,7 +418,7 @@ export interface ComplexSelectorProps<Value> extends Omit<
* - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
* @default 'attached' for input selectors; 'detached' for secondary and ghost selectors
*/
statusVariant?: FieldStatusVariant;
statusVariant?: FieldStatusPlacement;
/** Tooltip text displayed next to the label. */
labelTooltip?: string;
/** Trigger and field size. */
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/DateInput/DateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import {
inputStatusBorderStyles,
inputStatusHoverShadowStyles,
inputStatusFocusWithinStyles,
type FieldStatusVariant,
} from '../Field';
import {Icon} from '../Icon';
import {VisuallyHidden} from '../VisuallyHidden';
Expand Down Expand Up @@ -215,6 +214,7 @@ import {focusOutlineStyles} from '../utils/focusOutline.stylex';
import {useLocale, useTranslator} from '../i18n';

import {useMergedRefs} from '../hooks/useMergedRefs';
import type {FieldStatusPlacement} from '../Field/fieldStatusPlacement';
export interface DateInputProps extends Omit<
FieldInputBaseProps<HTMLInputElement>,
'onChange' | 'defaultValue'
Expand Down Expand Up @@ -344,7 +344,7 @@ export interface DateInputProps extends Omit<
* - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
* @default 'attached'
*/
statusVariant?: FieldStatusVariant;
statusVariant?: FieldStatusPlacement;

/**
* Width of the field. Numbers are treated as pixels, strings are used as-is
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/DateRangeInput/DateRangeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ import {
inputStatusBorderStyles,
inputStatusHoverShadowStyles,
inputStatusFocusWithinStyles,
type FieldStatusVariant,
} from '../Field';
import {Icon} from '../Icon';
import {Spinner} from '../Spinner';
Expand Down Expand Up @@ -82,6 +81,7 @@ import {themeProps} from '../utils/themeProps';
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
import {useLocale, useTranslator} from '../i18n';
import type {Locale} from '../i18n/types';
import type {FieldStatusPlacement} from '../Field/fieldStatusPlacement';

export type {DateRange} from '../Calendar';

Expand Down Expand Up @@ -443,7 +443,7 @@ export interface DateRangeInputProps extends Omit<
* - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
* @default 'attached'
*/
statusVariant?: FieldStatusVariant;
statusVariant?: FieldStatusPlacement;

/**
* Width of the field. Numbers are treated as pixels, strings are used as-is
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/Field/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import type {BaseProps} from '../BaseProps';
import type {SizeValue} from '../utils/types';
import {FieldLabel} from './FieldLabel';
import {FieldStatus} from '../FieldStatus/FieldStatus';
import type {FieldStatusVariant} from '../FieldStatus/FieldStatus';
import {useFieldStatusAnnouncement} from '../FieldStatus/useFieldStatusAnnouncement';
import {spacingVars, borderVars, sizeVars} from '../theme/tokens.stylex';
import type {IconType} from '../Icon';
Expand All @@ -32,6 +31,7 @@ import {FormLayoutContext} from '../FormLayout/FormLayoutContext';
import {Text} from '../Text/Text';
import {VisuallyHidden} from '../VisuallyHidden';
import {themeProps} from '../utils/themeProps';
import type {FieldStatusPlacement} from './fieldStatusPlacement';

const styles = stylex.create({
container: {
Expand Down Expand Up @@ -187,7 +187,7 @@ export interface FieldProps extends Omit<
* - 'tooltip': No message box; the input surfaces status through a tooltip on its on-field icon
* @default 'attached'
*/
statusVariant?: FieldStatusVariant;
statusVariant?: FieldStatusPlacement;
/**
* Width of the field. Numbers are treated as pixels, strings are used as-is
* (e.g. `'100%'`). Sizes the whole field — label, control, and status — so
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/Field/fieldStatusPlacement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

/**
* @file fieldStatusPlacement.ts
* @input The FieldStatus variant map
* @output FieldStatusPlacement, where a field control shows its status
* @position Field family type behind every field control's `statusVariant`
*
* A field control places its status message as a FieldStatus variant, or under
* `'tooltip'`, which the Field family consumes before rendering FieldStatus and
* shows through the control's on-field status icon (`component:FieldStatus`
* FR7, DEC-2). `'tooltip'` belongs here, not to FieldStatus itself, whose map
* keeps it only as a deprecated entry until the next minor.
*/

import type {FieldStatusVariantMap} from '../FieldStatus';

export type FieldStatusPlacement = keyof FieldStatusVariantMap | 'tooltip';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No package entry exports this, so @astryxdesign/richtext can't adopt it. RichTextEditor.tsx:373, and Lab's MobilePowerSearch.tsx:210, still declare statusVariant?: FieldStatusVariant, which the announced removal would narrow. AST-017 FR29c asks for maintained packages to migrate in the deprecation patch.

85 changes: 85 additions & 0 deletions packages/core/src/FieldStatus/FieldStatus.deprecation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

/**
* @file FieldStatus.deprecation.test.ts
* @input The TypeScript language service and FieldStatus.tsx
* @output Verifies an editor marks a direct `variant="tooltip"` deprecated,
* and leaves the supported variants and Field-family placements unmarked
* @position Static deprecation evidence for `component:FieldStatus` FR7
* (`spec:AST-017` FR29a: the warning lives in the declarations)
*/

import path from 'node:path';
import {fileURLToPath} from 'node:url';
import ts from 'typescript';
import {describe, expect, it} from 'vitest';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const CORE_ROOT = path.resolve(__dirname, '../..');
const FIXTURE = path.join(__dirname, '__deprecation_fixture__.tsx');

const SOURCE = `
import {FieldStatus} from './FieldStatus';
import type {FieldStatusVariant} from './FieldStatus';
import {TextInput} from '../TextInput/TextInput';

declare const someVariant: FieldStatusVariant;

export const direct = <FieldStatus type="error" message="m" variant="tooltip" />;
export const attached = <FieldStatus type="error" message="m" variant="attached" />;
export const detached = <FieldStatus type="error" message="m" variant="detached" />;
export const typed = <FieldStatus type="error" message="m" variant={someVariant} />;
export const field = (
<TextInput label="Name" value="" onChange={() => {}} status={{type: 'error', message: 'm'}} statusVariant="tooltip" />
);
`;

function deprecatedLines(): number[] {
const configPath = path.join(CORE_ROOT, 'tsconfig.json');
const config = ts.readConfigFile(configPath, ts.sys.readFile);
const parsed = ts.parseJsonConfigFileContent(
config.config,
ts.sys,
CORE_ROOT,
undefined,
configPath,
);
const files = new Map([[FIXTURE, SOURCE]]);
const host: ts.LanguageServiceHost = {
getScriptFileNames: () => [FIXTURE],
getScriptVersion: () => '1',
getScriptSnapshot: name => {
const text = files.get(name) ?? ts.sys.readFile(name);
return text == null ? undefined : ts.ScriptSnapshot.fromString(text);
},
getCurrentDirectory: () => CORE_ROOT,
getCompilationSettings: () => ({...parsed.options, noEmit: true}),
getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
fileExists: name => files.has(name) || ts.sys.fileExists(name),
readFile: name => files.get(name) ?? ts.sys.readFile(name),
readDirectory: ts.sys.readDirectory,
directoryExists: ts.sys.directoryExists,
getDirectories: ts.sys.getDirectories,
};
const service = ts.createLanguageService(host);
const errors = service.getSemanticDiagnostics(FIXTURE);
expect(
errors.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n')),
).toEqual([]);
const source = service.getProgram()!.getSourceFile(FIXTURE)!;
return service
.getSuggestionDiagnostics(FIXTURE)
.filter(d => d.reportsDeprecated)
.map(d => source.getLineAndCharacterOfPosition(d.start ?? 0).line);
}

function lineOf(name: string): number {
return SOURCE.split('\n').findIndex(line => line.includes(`const ${name} =`));
}

describe('FieldStatus tooltip deprecation (component:FieldStatus FR7)', () => {
it('marks a direct variant="tooltip" deprecated and nothing else', () => {
const lines = deprecatedLines();
expect(lines).toEqual([lineOf('direct')]);
}, 120_000);
});
29 changes: 26 additions & 3 deletions packages/core/src/FieldStatus/FieldStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,28 @@ export interface FieldStatusProps extends BaseProps<HTMLDivElement> {
variant?: FieldStatusVariant;
}

// A direct `tooltip` resolves to the deprecated signature first; every other
// call, a `FieldStatusVariant`-typed value included, resolves to the second,
// which `ComponentProps<typeof FieldStatus>` reads.
/**
* @deprecated FieldStatus has no `tooltip` presentation
* (`component:FieldStatus` FR7): it draws a plain message box with no tooltip.
* Set `statusVariant="tooltip"` on the field control instead, which shows the
* message from its on-field status icon. Removed in the next minor release.
*/
export function FieldStatus(
props: FieldStatusProps & {variant: 'tooltip'},
): React.JSX.Element;
/**
* A status message component for form fields: an `attached` message under a
* field, or a `detached` one with its own status icon.
*
* @example
* ```
* <FieldStatus type="error" message="This field is required" />
* ```
*/
export function FieldStatus(props: FieldStatusProps): React.JSX.Element;
/**
* A status message component for form fields.
*
Expand All @@ -151,9 +173,10 @@ export interface FieldStatusProps extends BaseProps<HTMLDivElement> {
* decorative for assistive tech (`aria-hidden`): the message text already names
* the status in words and is announced through the live region. The `attached`
* variant keeps its status affordance on the bordered input, so it renders no
* icon here to avoid a duplicate. The `tooltip` variant renders no message box
* at all — the input surfaces the status through a tooltip on its on-field
* icon — so callers skip rendering FieldStatus for it.
* icon here to avoid a duplicate. FieldStatus has no `tooltip` presentation:
* a field control given `statusVariant="tooltip"` surfaces the status through
* a tooltip on its on-field icon and renders no FieldStatus for it
* (`component:FieldStatus` FR7).
*
* Screen-reader announcements go through the persistent `useAnnounce` live
* regions (assertive for errors, polite otherwise) rather than `role`/
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/FieldStatus/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
export interface FieldStatusVariantMap {
attached: true;
detached: true;
/**
* @deprecated FieldStatus has no `tooltip` presentation
* (`component:FieldStatus` FR7): given it directly, it draws a plain message
* box with no tooltip. Set `statusVariant="tooltip"` on the field control
* (TextInput, Selector, and the rest), which shows the message from its
* on-field status icon. Removed in the next minor release.
*/
Comment on lines +28 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

TypeScript reports this tag only where the member is referenced (FieldStatusVariantMap['tooltip'], map.tooltip). <FieldStatus variant="tooltip"> and const v: FieldStatusVariant = 'tooltip' get no deprecation, and completions offer 'tooltip' unmarked (checked with the TypeScript 6.0.3 language service). A @deprecated FieldStatus overload for variant: 'tooltip' is flagged at that call alone. This text also says FieldStatus shows no message box, but it renders one: detached spacing, no glyph.

tooltip: true;
}

Expand Down
10 changes: 3 additions & 7 deletions packages/core/src/FileInput/FileInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,7 @@ import {
durationVars,
easeVars,
} from '../theme/tokens.stylex';
import {
Field,
InputClearButton,
type InputStatus,
type FieldStatusVariant,
} from '../Field';
import {Field, InputClearButton, type InputStatus} from '../Field';
import {Icon} from '../Icon';
import {Spinner} from '../Spinner';
import {VisuallyHidden} from '../VisuallyHidden';
Expand Down Expand Up @@ -70,6 +65,7 @@ import type {TranslatorFn} from '../i18n';
import {useMergedRefs} from '../hooks/useMergedRefs';
import {useListFormatter} from '../i18n/useListFormatter';
import {useResolvedRequired} from '../hooks/useResolvedRequired';
import type {FieldStatusPlacement} from '../Field/fieldStatusPlacement';
function formatFileSize(bytes: number, t: TranslatorFn): string {
if (bytes < 1024) {
return t('@astryx.fileInput.fileSize.bytes', {size: bytes});
Expand Down Expand Up @@ -406,7 +402,7 @@ export interface FileInputProps extends Omit<
* - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
* @default 'attached'
*/
statusVariant?: FieldStatusVariant;
statusVariant?: FieldStatusPlacement;
/**
* Description text displayed below the label.
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/MultiSelector/MultiSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import {
inputStatusBorderStyles,
inputStatusHoverShadowStyles,
inputWrapperStyles,
type FieldStatusVariant,
} from '../Field';
import {useKeepLayerOpenProps} from '../Layer/useLayer';
import {InternalInputClearButton} from '../Field/InputClearButton';
Expand Down Expand Up @@ -110,6 +109,7 @@ import type {AdaptivePresentation} from '../hooks/useAdaptivePresentation';
import {SelectorBottomSheet} from '../Selector/SelectorBottomSheet';
import {useSelectorPresentation} from '../Selector/useSelectorPresentation';
import {selectorPresentationStyles} from '../Selector/selectorPresentation.stylex';
import type {FieldStatusPlacement} from '../Field/fieldStatusPlacement';

// Sentinel value for the select-all item in keyboard navigation
const SELECT_ALL_VALUE = '__xds_select_all__';
Expand Down Expand Up @@ -807,7 +807,7 @@ export interface MultiSelectorProps<
* - 'tooltip': message is exposed from the on-field status icon
* @default 'attached' for input selectors; 'detached' for secondary and ghost selectors
*/
statusVariant?: FieldStatusVariant;
statusVariant?: FieldStatusPlacement;

/**
* Width of the field. Numbers are treated as pixels, strings are used as-is
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/NumberInput/NumberInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import {
inputStatusBorderStyles,
inputStatusHoverShadowStyles,
inputStatusFocusWithinStyles,
type FieldStatusVariant,
} from '../Field';
import {Icon, renderIconSlot, type IconType} from '../Icon';
import {VisuallyHidden} from '../VisuallyHidden';
Expand Down Expand Up @@ -235,6 +234,7 @@ import {themeProps} from '../utils/themeProps';
import {useTranslator, useLocale} from '../i18n';
import {formatEditableNumber} from './numberParser';
import {parseNumberInput, resolveNumberInputCommit} from './numberInputCommit';
import type {FieldStatusPlacement} from '../Field/fieldStatusPlacement';

interface NumberInputPropsBase extends Omit<
FieldInputBaseProps,
Expand Down Expand Up @@ -324,7 +324,7 @@ interface NumberInputPropsBase extends Omit<
* - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
* @default 'attached'
*/
statusVariant?: FieldStatusVariant;
statusVariant?: FieldStatusPlacement;
/**
* The size of the input.
* - 'sm': Compact size (28px height)
Expand Down
Loading
Loading