Skip to content

Commit c0ded8a

Browse files
authored
Merge pull request #153 from prysmex/pagination
feat: eui-i18n and eui-pagination
2 parents 99f3137 + 157bdab commit c0ded8a

File tree

29 files changed

+810
-4
lines changed

29 files changed

+810
-4
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{{#let this.lookupedTokens as |result|}}
2+
{{#if this.isI18nTokensShape}}
3+
{{#if this.i18n.renderComponent}}
4+
{{yield
5+
(component
6+
(ensure-safe-component this.i18n.renderComponent) tokens=result
7+
)
8+
}}
9+
{{else}}
10+
{{#each
11+
(if (not-eq (type-of result) "array") (array result) result)
12+
as |token index|
13+
}}
14+
{{yield (component "eui-i18n/render" token=token) index result}}
15+
{{/each}}
16+
{{/if}}
17+
{{else}}
18+
{{#if this.i18n.renderComponent}}
19+
{{yield
20+
(component
21+
(ensure-safe-component this.i18n.renderComponent) tokens=result
22+
)
23+
}}
24+
{{else}}
25+
{{#each
26+
(if (not-eq (type-of result) "array") (array result) result)
27+
as |token index|
28+
}}
29+
{{yield (component "eui-i18n/render" token=token) index result}}
30+
{{/each}}
31+
{{/if}}
32+
{{/if}}
33+
{{/let}}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import Component from '@glimmer/component';
2+
import { processStringToChildren } from './util';
3+
//@ts-ignore
4+
import { i18n, I18nShape } from '@ember-eui/core/i18n';
5+
6+
interface Args {
7+
tokens?: string[];
8+
defaults?: string[];
9+
token?: string;
10+
default?: string;
11+
values?: { [key: string]: any };
12+
i18n?: I18nShape;
13+
}
14+
15+
interface lookupTokenOptions {
16+
token: string;
17+
i18nMapping: I18nShape['mapping'];
18+
valueDefault: string;
19+
i18nMappingFunc?: (token: string) => string;
20+
values?: { [key: string]: any };
21+
render?: I18nShape['render'];
22+
}
23+
24+
function lookupToken(options: lookupTokenOptions) {
25+
const {
26+
token,
27+
i18nMapping,
28+
valueDefault,
29+
i18nMappingFunc,
30+
values = {}
31+
} = options;
32+
33+
let renderable = (i18nMapping && i18nMapping[token]) || valueDefault;
34+
35+
const children = processStringToChildren(renderable, values, i18nMappingFunc);
36+
if (typeof children === 'string') {
37+
// likewise, `processStringToChildren` returns a string or ReactChild[] depending on
38+
// the type of `values`, so we will make the assumption that the default value is correct.
39+
return children;
40+
}
41+
42+
// same reasons as above, we can't promise the transforms match the default's type
43+
return children;
44+
}
45+
46+
export default class EuiI18nComponent extends Component<Args> {
47+
get isI18nTokensShape() {
48+
return this.args.tokens != null;
49+
}
50+
51+
get i18n() {
52+
return this.args.i18n || i18n;
53+
}
54+
55+
get lookupedTokens() {
56+
if (this.isI18nTokensShape) {
57+
return this.args.tokens?.map((token, idx) =>
58+
lookupToken({
59+
token,
60+
i18nMapping: i18n.mapping,
61+
valueDefault: this.args.defaults![idx]
62+
})
63+
);
64+
} else {
65+
return lookupToken({
66+
token: this.args.token!,
67+
i18nMapping: i18n.mapping,
68+
valueDefault: this.args.default!,
69+
values: this.args.values
70+
});
71+
}
72+
}
73+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{yield @token}}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
import {
10+
isBoolean,
11+
isString,
12+
isNumber,
13+
isUndefined
14+
} from '../../utils/predicate';
15+
import { get } from '@ember/object';
16+
import { assert } from '@ember/debug';
17+
18+
function isPrimitive(value: any) {
19+
return (
20+
isBoolean(value) || isString(value) || isNumber(value) || isUndefined(value)
21+
);
22+
}
23+
24+
type Child = string | { propName: string };
25+
26+
function hasPropName(child: Child): child is { propName: string } {
27+
return child
28+
? typeof child === 'object' && child.hasOwnProperty('propName')
29+
: false;
30+
}
31+
32+
function isElement(value: any) {
33+
return value.hasOwnProperty('propName') && !isPrimitive(value);
34+
}
35+
36+
/**
37+
* Replaces placeholder values in `input` with their matching value in `values`
38+
* e.g. input:'Hello, {name}' will replace `{name}` with `values[name]`
39+
* @param {string} input
40+
* @param {RenderableValues} values
41+
* @param {Function} i18nMappingFunc
42+
* @returns {string | React.ReactChild[]}
43+
*/
44+
export function processStringToChildren(
45+
input: string,
46+
values: { [key: string]: any },
47+
i18nMappingFunc?: (token: string) => string
48+
): any[] | string {
49+
const children: any[] = [];
50+
51+
let child: Child | undefined = { propName: '' };
52+
53+
function appendCharToChild(char: string) {
54+
if (child === undefined) {
55+
// starting a new string literal
56+
child = char;
57+
} else if (typeof child === 'string') {
58+
// existing string literal
59+
child = child + char;
60+
} else if (hasPropName(child)) {
61+
// adding to the propName of a values lookup
62+
child.propName = child.propName + char;
63+
}
64+
}
65+
66+
function appendValueToChildren(value?: Child) {
67+
if (value === undefined) {
68+
return;
69+
} else if (hasPropName(value)) {
70+
// an array with any ReactElements will be kept as an array
71+
// so they need to be assigned a key
72+
// children.push(cloneElement(value, { key: children.length }));
73+
children.push(value.propName);
74+
} else if (isElement(value)) {
75+
// this won't be called, propName children are converted to a ReactChild before calling this
76+
} else {
77+
// everything else can go straight in
78+
if (i18nMappingFunc !== undefined && typeof value === 'string') {
79+
value = i18nMappingFunc(value);
80+
}
81+
children.push(value);
82+
}
83+
}
84+
85+
// if we don't encounter a non-primitive
86+
// then `children` can be concatenated together at the end
87+
let encounteredNonPrimitive = false;
88+
for (let i = 0; i < input.length; i++) {
89+
const char = input[i];
90+
91+
if (char === '\\') {
92+
// peek at the next character to know if this is an escape
93+
const nextChar = input[i + 1];
94+
let charToAdd = char; // if this isn't an escape sequence then we will add the backslash
95+
96+
if (nextChar === '{' || nextChar === '}') {
97+
// escaping a brace
98+
i += 1; // advance passed the brace
99+
charToAdd = input[i];
100+
}
101+
appendCharToChild(charToAdd);
102+
} else if (char === '{') {
103+
appendValueToChildren(child);
104+
child = { propName: '' };
105+
} else if (char === '}') {
106+
const propName = (child as { propName: string }).propName as string;
107+
if (get(values, propName) === undefined) {
108+
assert(
109+
`Key "${propName}" not found in ${JSON.stringify(values, null, 2)}`,
110+
true
111+
);
112+
}
113+
const propValue = values[propName];
114+
encounteredNonPrimitive =
115+
encounteredNonPrimitive || !isPrimitive(propValue);
116+
appendValueToChildren(propValue);
117+
child = undefined;
118+
} else {
119+
appendCharToChild(char);
120+
}
121+
}
122+
123+
// include any remaining child value
124+
appendValueToChildren(child);
125+
126+
return encounteredNonPrimitive ? children : children.join('');
127+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{{#let (add @pageIndex 1) as |pageNumber|}}
2+
<EuiI18n
3+
@token="euiPaginationButton.longPageString"
4+
@default="Page {page} of {totalPages}"
5+
@values={{hash page=pageNumber totalPages=@totalPages}}
6+
as |Token|
7+
>
8+
<Token as |longPageString|>
9+
<EuiI18n
10+
@token="euiPaginationButton.shortPageString"
11+
@default="Page {page}"
12+
values={{hash page=pageNumber}}
13+
as |InnerToken|
14+
>
15+
<InnerToken as |shortPageString|>
16+
<EuiButtonEmpty
17+
class={{class-names
18+
"euiPaginationButton"
19+
(if @isActive "euiPaginationButton-isActive")
20+
(if @isPlaceholder "euiPaginationButton-isPlaceholder")
21+
(if @hideOnMobile "euiPaginationButton--hideOnMobile")
22+
}}
23+
aria-label={{if @totalPages longPageString shortPageString}}
24+
@size="s"
25+
@color="text"
26+
@isDisabled={{or @isPlaceholder @isActive}}
27+
aria-current={{if @isActive true}}
28+
...attributes
29+
>
30+
{{pageNumber}}
31+
</EuiButtonEmpty>
32+
</InnerToken>
33+
</EuiI18n>
34+
</Token>
35+
</EuiI18n>
36+
{{/let}}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{{#if (arg-or-default @inList true)}}
2+
<li>
3+
<EuiPaginationButton
4+
@isActive={{eq @pageIndex @props.activePage}}
5+
@totalPages={{@props.pageCount}}
6+
@pageIndex={{@pageIndex}}
7+
aria-controls={{@props.ariaControls}}
8+
@hideOnMobile={{true}}
9+
{{on "click" (fn @props.safeClick @pageIndex)}}
10+
/>
11+
</li>
12+
{{else}}
13+
<EuiPaginationButton
14+
@isActive={{eq @pageIndex @props.activePage}}
15+
@totalPages={{@props.pageCount}}
16+
@pageIndex={{@pageIndex}}
17+
aria-controls={{@props.ariaControls}}
18+
@hideOnMobile={{true}}
19+
{{on "click" (fn @props.safeClick @pageIndex)}}
20+
/>
21+
{{/if}}

0 commit comments

Comments
 (0)