Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EuiContext & I18n #1404

Merged
merged 17 commits into from
Jan 17, 2019
Merged
Show file tree
Hide file tree
Changes from 4 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
7 changes: 5 additions & 2 deletions scripts/babel/proptypes-from-ts-props/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ function getPropTypesForNode(node, optional, state) {
[
types.objectExpression(
node.members.map(property => {
// skip TS index signatures
if (types.isTSIndexSignature(property)) return null;

const objectProperty = types.objectProperty(
types.identifier(property.key.name || `"${property.key.value}"`),
getPropTypesForNode(property.typeAnnotation, property.optional, state)
Expand All @@ -354,7 +357,7 @@ function getPropTypesForNode(node, optional, state) {
objectProperty.leadingComments = property.leadingComments.map(({ type, value }) => ({ type, value }));
}
return objectProperty;
})
}).filter(x => x != null)
)
]
);
Expand Down Expand Up @@ -967,7 +970,7 @@ module.exports = function propTypesFromTypeScript({ types }) {
processComponentDeclaration(idTypeAnnotation.typeAnnotation.typeParameters.params[0], nodePath, state);
fileCodeNeedsUpdating = true;
} else {
throw new Error(`Cannot process annotation id React.${right.name}`);
// throw new Error(`Cannot process annotation id React.${right.name}`);
}
}
} else if (idTypeAnnotation.typeAnnotation.typeName.type === 'Identifier') {
Expand Down
4 changes: 4 additions & 0 deletions src-docs/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ import { ColorPickerExample }
import { ComboBoxExample }
from './views/combo_box/combo_box_example';

import { ContextExample }
from './views/context/context_example';

import { ContextMenuExample }
from './views/context_menu/context_menu_example';

Expand Down Expand Up @@ -405,6 +408,7 @@ const navigation = [{
items: [
AccessibilityExample,
ColorPaletteExample,
ContextExample,
CopyExample,
UtilityClassesExample,
DelayHideExample,
Expand Down
73 changes: 73 additions & 0 deletions src-docs/src/views/context/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { Component, Fragment } from 'react';

import {
EuiContext,
EuiButton,
EuiButtonEmpty,
EuiFieldText,
EuiSpacer,
I18n,
I18nNumber,
} from '../../../../src/components';

const mappings = {
fr: {
greeting: 'Salutations!',
guestNo: 'Vous êtes invité #',
question: 'Quel est votre nom?',
placeholder: 'Jean Dupont',
action: 'Soumettre',
},
};

export default class extends Component {
state = {
language: 'en',
name: ''
}

setLanguage = (language) => this.setState({ language })

render() {
const i18n = {
mapping: mappings[this.state.language],
formatNumber: (value) => new Intl.NumberFormat(this.state.language).format(value),
};

return (
<EuiContext i18n={i18n}>
<div>
<EuiButtonEmpty onClick={() => this.setLanguage('en')}>English</EuiButtonEmpty>
<EuiButtonEmpty onClick={() => this.setLanguage('fr')}>French</EuiButtonEmpty>
chandlerprall marked this conversation as resolved.
Show resolved Hide resolved

<EuiSpacer size="m"/>

<strong><I18n token="greeting" default="Welcome!"/></strong>

<EuiSpacer size="s"/>

<p><I18n token="guestNo" default="You are guest #"/><I18nNumber value={1582394}/></p>

<EuiSpacer size="m"/>

<I18n token="question" default="What is your name?">{question => <p>{question}</p>}</I18n>

<EuiSpacer size="s"/>

<I18n tokens={['placeholder', 'action']} defaults={['John Doe', 'Submit']}>
{([placeholder, action]) => (
<Fragment>
<EuiFieldText
placeholder={placeholder}
value={this.state.name}
/><EuiSpacer size="m" />

<EuiButton>{action}</EuiButton>
</Fragment>
)}
</I18n>
</div>
</EuiContext>
);
}
}
40 changes: 40 additions & 0 deletions src-docs/src/views/context/context_example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';

import { renderToHtml } from '../../services';

import {
GuideSectionTypes,
} from '../../components';

import {
EuiCode,
EuiContext,
I18n,
} from '../../../../src/components';

import Context from './context';
const contextSource = require('!!raw-loader!./context');
const contextHtml = renderToHtml(Context);

export const ContextExample = {
title: 'Context',
sections: [{
source: [{
type: GuideSectionTypes.JS,
code: contextSource,
}, {
type: GuideSectionTypes.HTML,
code: contextHtml,
}],
text: (
<p>
<EuiCode>EuiContext</EuiCode> allows setting global internationalization copy for
EUI components. Any components used within this context will lookup their display values
from this mapping.
</p>
),
components: { EuiContext },
demo: <Context />,
props: { EuiContext, I18n },
}],
};
25 changes: 25 additions & 0 deletions src/components/context/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { createContext, ReactChild } from 'react';

export interface I18nShape {
mapping?: {
[key: string]: ReactChild;
};
formatNumber?: (x: number) => string;
formatDateTime?: (x: Date) => string;
}

const I18nContext: React.Context<I18nShape> = createContext({});
const { Provider: EuiI18nProvider, Consumer: EuiI18nConsumer } = I18nContext;

interface IEuiContextProps {
i18n: I18nShape;
children: React.ReactNode;
}

const EuiContext: React.SFC<IEuiContextProps> = ({i18n = {}, children}) => (
<EuiI18nProvider value={i18n}>
{children}
</EuiI18nProvider>
);

export { EuiContext, EuiI18nConsumer };
4 changes: 4 additions & 0 deletions src/components/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {
EuiContext,
EuiI18nConsumer
} from './context';
52 changes: 52 additions & 0 deletions src/components/i18n/i18n.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { ReactChild, ReactElement } from 'react';
import { EuiI18nConsumer } from '../context';
import { ExclusiveUnion } from '../common';
import { I18nShape } from '../context/context';

// <I18n token="foo"/>
// <I18n token="foo">{(foo) => <p>foo</p>}</I18n>
// <I18n tokens=['foo', 'bar']>{([foo, bar]) => <p>{foo}, {bar}</p></I18n>

function lookupToken(token: string, i18nMapping: I18nShape['mapping'], valueDefault: ReactChild) {
return (i18nMapping && i18nMapping[token]) || valueDefault;
}

interface I18nTokenShape {
token: string;
default: ReactChild;
children?: (x: ReactChild) => ReactElement<any>;
}

interface I18nTokensShape {
tokens: string[];
defaults: ReactChild[];
children: (x: ReactChild[]) => ReactElement<any>;
}

type I18nProps = ExclusiveUnion<I18nTokenShape, I18nTokensShape>;

function hasTokens(x: I18nProps): x is I18nTokensShape {
return x.tokens != null;
}

const I18n: React.SFC<I18nProps> = (props) => (
<EuiI18nConsumer>
{
(i18nConfig) => {
const { mapping } = i18nConfig;
if (hasTokens(props)) {
return props.children(props.tokens.map((token, idx) => lookupToken(token, mapping, props.defaults[idx])));
}

const tokenValue = lookupToken(props.token, mapping, props.default);
if (props.children) {
return props.children(tokenValue);
} else {
return tokenValue;
}
}
}
</EuiI18nConsumer>
);

export { I18n };
47 changes: 47 additions & 0 deletions src/components/i18n/i18n_number.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { ReactChild, ReactElement } from 'react';
import { EuiI18nConsumer } from '../context';
import { ExclusiveUnion } from '../common';

const defaultFormatter = new Intl.NumberFormat('en');
function defaultFormatNumber(value: number) {
return defaultFormatter.format(value);
}

interface I18nNumberValueShape {
value: number;
children?: (x: ReactChild) => ReactElement<any>;
}

interface I18nNumberValuesShape {
values: number[];
children: (x: ReactChild[]) => ReactElement<any>;
}

type I18nNumberProps = ExclusiveUnion<I18nNumberValueShape, I18nNumberValuesShape>;

function hasValues(x: I18nNumberProps): x is I18nNumberValuesShape {
return x.values != null;
}

const I18nNumber: React.SFC<I18nNumberProps> = (props) => (
<EuiI18nConsumer>
{
(i18nConfig) => {
const formatNumber = i18nConfig.formatNumber || defaultFormatNumber;

if (hasValues(props)) {
return props.children(props.values.map(value => formatNumber(value)));
}

const formattedValue = (formatNumber || defaultFormatNumber)(props.value);
if (props.children) {
return props.children(formattedValue);
} else {
return formattedValue;
}
}
}
</EuiI18nConsumer>
);

export { I18nNumber };
2 changes: 2 additions & 0 deletions src/components/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { I18n } from './i18n';
export { I18nNumber } from './i18n_number';
10 changes: 10 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ export {
EuiComboBox,
} from './combo_box';

export {
EuiContext,
EuiI18nConsumer
} from './context';

export {
EuiContextMenu,
EuiContextMenuPanel,
Expand Down Expand Up @@ -181,6 +186,11 @@ export {
EuiImage,
} from './image';

export {
I18n,
I18nNumber,
} from './i18n';

export {
EuiLoadingKibana,
EuiLoadingChart,
Expand Down