diff --git a/CHANGELOG.md b/CHANGELOG.md index e678eb4414d..7d2ba6d4f2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ **Bug fixes** - Fixed `EuiComboBox` to not pass its `inputRef` prop down to the DOM ([#1867](https://github.com/elastic/eui/pull/1867)) +- Fixed type definitions around `EuiI18n`'s `default` prop to better support use cases ([#1861](https://github.com/elastic/eui/pull/1861)) ## [`10.1.0`](https://github.com/elastic/eui/tree/v10.1.0) diff --git a/src/components/context/context.tsx b/src/components/context/context.tsx index e6995e2c338..18325a5b3e9 100644 --- a/src/components/context/context.tsx +++ b/src/components/context/context.tsx @@ -8,7 +8,7 @@ export type Renderable = ReactChild | ((values: T) => ReactChild); export interface I18nShape { mapping?: { - [key: string]: Renderable; + [key: string]: Renderable; }; mappingFunc?: (value: string) => string; formatNumber?: (x: number) => string; diff --git a/src/components/i18n/__snapshots__/i18n.test.tsx.snap b/src/components/i18n/__snapshots__/i18n.test.tsx.snap index 0ff1966ccce..19f03d22b70 100644 --- a/src/components/i18n/__snapshots__/i18n.test.tsx.snap +++ b/src/components/i18n/__snapshots__/i18n.test.tsx.snap @@ -150,7 +150,9 @@ exports[`EuiI18n reading values from context mappingFunc calls the mapping funct default="This is the basic string." token="test1" > -
+
THIS IS THE BASIC STRING.
diff --git a/src/components/i18n/i18n.test.tsx b/src/components/i18n/i18n.test.tsx index fdfadc24b68..41992e448bd 100644 --- a/src/components/i18n/i18n.test.tsx +++ b/src/components/i18n/i18n.test.tsx @@ -67,7 +67,7 @@ describe('EuiI18n', () => { ); const component = mount( - {(result: ReactChild) => `Here's something neat: ${result}`} + {(result: string) => `Here's something neat: ${result}`} ); expect(component).toMatchSnapshot(); @@ -226,7 +226,7 @@ describe('EuiI18n', () => { mappingFunc: (value: string) => value.toUpperCase(), }}> - {(one: ReactChild) =>
{one}
} + {(one: string) =>
{one}
}
); diff --git a/src/components/i18n/i18n.tsx b/src/components/i18n/i18n.tsx index 06f2dac5389..5d8a89c6175 100644 --- a/src/components/i18n/i18n.tsx +++ b/src/components/i18n/i18n.tsx @@ -8,43 +8,58 @@ function throwError(): never { throw new Error('asdf'); } -function lookupToken( +function lookupToken< + T extends RenderableValues, + DEFAULT extends Renderable, + RESOLVED extends ResolvedType +>( token: string, i18nMapping: I18nShape['mapping'], - valueDefault: Renderable, + valueDefault: DEFAULT, i18nMappingFunc?: (token: string) => string, - values?: I18nTokenShape['values'] -): ReactChild { + values?: I18nTokenShape['values'] +): RESOLVED { let renderable = (i18nMapping && i18nMapping[token]) || valueDefault; if (typeof renderable === 'function') { if (values === undefined) { return throwError(); } else { + // @ts-ignore-next-line + // TypeScript complains that `DEFAULT` doesn't have a call signature + // but we verified `renderable` is a function return renderable(values); } } else if (values === undefined || typeof renderable !== 'string') { if (i18nMappingFunc && typeof valueDefault === 'string') { renderable = i18nMappingFunc(valueDefault); } - return renderable; + // there's a hole in the typings here as there is no guarantee that i18nMappingFunc + // returned the same type of the default value, but we need to keep that assumption + return renderable as RESOLVED; } const children = processStringToChildren(renderable, values, i18nMappingFunc); if (typeof children === 'string') { - return children; + // likewise, `processStringToChildren` returns a string or ReactChild[] depending on + // the type of `values`, so we will make the assumption that the default value is correct. + return children as RESOLVED; } const Component: FunctionComponent = () => { return {children}; }; - return React.createElement(Component, values); + + // same reasons as above, we can't promise the transforms match the default's type + return React.createElement(Component, values) as RESOLVED; } -interface I18nTokenShape { +type ResolvedType = T extends (...args: any[]) => any ? ReturnType : T; + +interface I18nTokenShape> { token: string; - default: Renderable; - children?: (x: ReactChild) => ReactChild; + default: DEFAULT; + children?: (x: ResolvedType) => ReactChild; values?: T; } @@ -54,16 +69,21 @@ interface I18nTokensShape { children: (x: ReactChild[]) => ReactChild; } -type EuiI18nProps = ExclusiveUnion, I18nTokensShape>; +type EuiI18nProps> = ExclusiveUnion< + I18nTokenShape, + I18nTokensShape +>; -function hasTokens(x: EuiI18nProps): x is I18nTokensShape { +function hasTokens(x: EuiI18nProps): x is I18nTokensShape { return x.tokens != null; } // Must use the generics // If instead typed with React.FunctionComponent there isn't feedback given back to the dev // when using a `values` object with a renderer callback. -const EuiI18n = (props: EuiI18nProps) => ( +const EuiI18n = >( + props: EuiI18nProps +) => ( {i18nConfig => { const { mapping, mappingFunc } = i18nConfig;