@astryxdesign/lab 0.6.1-canary.f48d468 → 0.6.2-canary.a4bcbeb
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatTypingIndicator.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatTypingIndicator.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ChatTypingIndicator.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatTypingIndicator.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAMlD,MAAM,WAAW,wBAAyB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACzE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAuFD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,MAAM,EACN,SAAS,EACT,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,GACJ,EAAE,wBAAwB,+BA4B1B;yBAnCe,mBAAmB"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file ChatTypingIndicator.tsx
|
|
7
|
-
* @input Uses React, StyleX (keyframes), theme tokens
|
|
7
|
+
* @input Uses React, i18n (useTranslator/useLocale), StyleX (keyframes), theme tokens
|
|
8
8
|
* @output Exports ChatTypingIndicator component and ChatTypingIndicatorProps
|
|
9
9
|
* @position Animated "X is typing…" hint above a chat composer
|
|
10
10
|
*
|
|
@@ -18,11 +18,13 @@
|
|
|
18
18
|
* - /packages/lab/src/Chat/ChatTypingIndicator.doc.mjs
|
|
19
19
|
* - /apps/storybook/stories/ChatAdditions.stories.tsx (examples)
|
|
20
20
|
*/
|
|
21
|
+
import { useMemo } from 'react';
|
|
21
22
|
import * as stylex from '@stylexjs/stylex';
|
|
22
23
|
import '@astryxdesign/core/theme/tokens.stylex';
|
|
23
24
|
import { colorVars, spacingVars, radiusVars, durationVars, typographyVars, typeScaleVars } from '@astryxdesign/core/theme/tokens.stylex';
|
|
24
25
|
import { mergeProps } from '@astryxdesign/core/utils';
|
|
25
26
|
import { themeProps } from '@astryxdesign/core/utils';
|
|
27
|
+
import { useLocale, useTranslator } from '@astryxdesign/core/i18n';
|
|
26
28
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
27
29
|
// =============================================================================
|
|
28
30
|
// Styles
|
|
@@ -38,17 +40,31 @@ const styles = {
|
|
|
38
40
|
$$css: true
|
|
39
41
|
}
|
|
40
42
|
};
|
|
41
|
-
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Builds the status sentence for the people currently typing.
|
|
46
|
+
*
|
|
47
|
+
* Every phrase comes from the translation catalog, and the names themselves are
|
|
48
|
+
* joined by `Intl.ListFormat` for the active locale rather than an English
|
|
49
|
+
* "and". Three or more people collapse to the first name plus a translated
|
|
50
|
+
* overflow phrase, which is then joined the same way — so the conjunction, the
|
|
51
|
+
* separator, and their order all follow the locale instead of this file.
|
|
52
|
+
*/
|
|
53
|
+
function typingLabel(names, t, listFormat) {
|
|
42
54
|
if (names == null || names.length === 0) {
|
|
43
55
|
return null;
|
|
44
56
|
}
|
|
45
57
|
if (names.length === 1) {
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return `${names[0]} and ${names[1]} are typing…`;
|
|
58
|
+
return t('@astryx.chatTypingIndicator.one', {
|
|
59
|
+
name: names[0]
|
|
60
|
+
});
|
|
50
61
|
}
|
|
51
|
-
|
|
62
|
+
const parts = names.length === 2 ? [names[0], names[1]] : [names[0], t('@astryx.chatTypingIndicator.others', {
|
|
63
|
+
count: names.length - 1
|
|
64
|
+
})];
|
|
65
|
+
return t('@astryx.chatTypingIndicator.many', {
|
|
66
|
+
names: listFormat.format(parts)
|
|
67
|
+
});
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
// =============================================================================
|
|
@@ -76,7 +92,13 @@ export function ChatTypingIndicator({
|
|
|
76
92
|
'data-testid': testId,
|
|
77
93
|
ref
|
|
78
94
|
}) {
|
|
79
|
-
const
|
|
95
|
+
const t = useTranslator();
|
|
96
|
+
const locale = useLocale();
|
|
97
|
+
const listFormat = useMemo(() => new Intl.ListFormat(locale, {
|
|
98
|
+
style: 'long',
|
|
99
|
+
type: 'conjunction'
|
|
100
|
+
}), [locale]);
|
|
101
|
+
const label = typingLabel(names, t, listFormat);
|
|
80
102
|
return /*#__PURE__*/_jsxs("div", {
|
|
81
103
|
ref: ref,
|
|
82
104
|
role: "status",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/lab",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2-canary.a4bcbeb",
|
|
4
4
|
"description": "Experimental Astryx components — published to npm only under the @canary dist-tag for early testing; never released as a stable (latest) version.",
|
|
5
5
|
"author": "Meta Open Source",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@stylexjs/stylex": ">=0.10.0",
|
|
49
|
-
"@astryxdesign/core": "0.6.
|
|
49
|
+
"@astryxdesign/core": "0.6.2-canary.a4bcbeb",
|
|
50
50
|
"react": ">=19.0.0",
|
|
51
51
|
"react-dom": ">=19.0.0"
|
|
52
52
|
},
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"d3-shape": "^3.2.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@astryxdesign/a11y-spec": "0.6.
|
|
60
|
-
"@astryxdesign/cli": "0.6.
|
|
59
|
+
"@astryxdesign/a11y-spec": "0.6.2-canary.a4bcbeb",
|
|
60
|
+
"@astryxdesign/cli": "0.6.2-canary.a4bcbeb",
|
|
61
61
|
"@babel/cli": "^7.29.7",
|
|
62
62
|
"@babel/core": "^7.29.7",
|
|
63
63
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -8,12 +8,12 @@ export const docs = {
|
|
|
8
8
|
group: 'Chat',
|
|
9
9
|
category: 'Chat',
|
|
10
10
|
displayName: 'Chat Typing Indicator',
|
|
11
|
-
description: 'Animated three-dot typing hint with a grammar-aware label: "Ana is typing...", "Ana and Ben are typing...", or "Ana and 2 others are typing...". Dots bounce with staggered stylex.keyframes delays, disabled under prefers-reduced-motion; the label is announced politely via role="status".',
|
|
11
|
+
description: 'Animated three-dot typing hint with a localized, grammar-aware label: "Ana is typing...", "Ana and Ben are typing...", or "Ana and 2 others are typing...". Dots bounce with staggered stylex.keyframes delays, disabled under prefers-reduced-motion; the label is announced politely via role="status".',
|
|
12
12
|
props: [
|
|
13
13
|
{
|
|
14
14
|
name: 'names',
|
|
15
15
|
type: 'string[]',
|
|
16
|
-
description: 'Names of people currently typing. One name renders "is typing", two render both names, more collapse to "and N others". When omitted or empty, only the animated dots render.',
|
|
16
|
+
description: 'Names of people currently typing. One name renders "is typing", two render both names, more collapse to "and N others". The sentence comes from the i18n catalog and the names are joined with Intl.ListFormat, so the wording and conjunction follow the active locale. When omitted or empty, only the animated dots render.',
|
|
17
17
|
},
|
|
18
18
|
],
|
|
19
19
|
};
|
|
@@ -23,7 +23,7 @@ export const docsZh = {
|
|
|
23
23
|
displayName: 'Chat Typing Indicator',
|
|
24
24
|
description: '带语法感知标签的三点跳动输入提示:"Ana is typing..."、"Ana and Ben are typing..." 或 "Ana and 2 others are typing..."。圆点用 stylex.keyframes 交错跳动,prefers-reduced-motion 下禁用;标签通过 role="status" 礼貌播报。',
|
|
25
25
|
propDescriptions: {
|
|
26
|
-
names: '正在输入的人名。一个名字渲染 "is typing",两个渲染双名,更多折叠为 "and N others"
|
|
26
|
+
names: '正在输入的人名。一个名字渲染 "is typing",两个渲染双名,更多折叠为 "and N others"。语句取自 i18n 词条,人名由 Intl.ListFormat 按当前语言拼接,因此措辞与连接词随语言而变。省略或为空时仅渲染动画圆点。',
|
|
27
27
|
},
|
|
28
28
|
};
|
|
29
29
|
|
|
@@ -32,6 +32,7 @@ export const docsDense = {
|
|
|
32
32
|
displayName: 'Chat Typing Indicator',
|
|
33
33
|
description: 'bouncing three-dot typing hint; label grammar 1/2/N names; reduced-motion safe keyframes; role=status polite announce; dots-only when names empty',
|
|
34
34
|
propDescriptions: {
|
|
35
|
-
names:
|
|
35
|
+
names:
|
|
36
|
+
"string[]; 1='X is typing', 2='X and Y', 3+='X and N others'; phrases from i18n catalog, names joined by Intl.ListFormat; empty=dots only",
|
|
36
37
|
},
|
|
37
38
|
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {describe, it, expect} from 'vitest';
|
|
4
4
|
import {render, screen} from '@testing-library/react';
|
|
5
5
|
import {ChatTypingIndicator} from './ChatTypingIndicator';
|
|
6
|
+
import {InternationalizationProvider} from '@astryxdesign/core/i18n';
|
|
6
7
|
|
|
7
8
|
describe('ChatTypingIndicator', () => {
|
|
8
9
|
it('renders "X is typing…" for one name', () => {
|
|
@@ -20,6 +21,51 @@ describe('ChatTypingIndicator', () => {
|
|
|
20
21
|
expect(screen.getByText('Ana and 2 others are typing…')).toBeTruthy();
|
|
21
22
|
});
|
|
22
23
|
|
|
24
|
+
it('localizes the one-name sentence through the i18n catalog', () => {
|
|
25
|
+
render(
|
|
26
|
+
<InternationalizationProvider
|
|
27
|
+
locale="fr"
|
|
28
|
+
overrides={{
|
|
29
|
+
fr: {
|
|
30
|
+
'@astryx.chatTypingIndicator.one': '{name} est en train d’écrire…',
|
|
31
|
+
},
|
|
32
|
+
}}>
|
|
33
|
+
<ChatTypingIndicator names={['Ana']} />
|
|
34
|
+
</InternationalizationProvider>,
|
|
35
|
+
);
|
|
36
|
+
expect(screen.getByText('Ana est en train d’écrire…')).toBeTruthy();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('joins two names with the locale list format', () => {
|
|
40
|
+
render(
|
|
41
|
+
<InternationalizationProvider
|
|
42
|
+
locale="fr"
|
|
43
|
+
overrides={{
|
|
44
|
+
fr: {'@astryx.chatTypingIndicator.many': '{names} écrivent…'},
|
|
45
|
+
}}>
|
|
46
|
+
<ChatTypingIndicator names={['Ana', 'Ben']} />
|
|
47
|
+
</InternationalizationProvider>,
|
|
48
|
+
);
|
|
49
|
+
// Intl.ListFormat('fr') joins with "et", not the English "and".
|
|
50
|
+
expect(screen.getByText('Ana et Ben écrivent…')).toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('localizes the collapsed overflow sentence', () => {
|
|
54
|
+
render(
|
|
55
|
+
<InternationalizationProvider
|
|
56
|
+
locale="fr"
|
|
57
|
+
overrides={{
|
|
58
|
+
fr: {
|
|
59
|
+
'@astryx.chatTypingIndicator.others': '{count, number} autres',
|
|
60
|
+
'@astryx.chatTypingIndicator.many': '{names} écrivent…',
|
|
61
|
+
},
|
|
62
|
+
}}>
|
|
63
|
+
<ChatTypingIndicator names={['Ana', 'Ben', 'Casey']} />
|
|
64
|
+
</InternationalizationProvider>,
|
|
65
|
+
);
|
|
66
|
+
expect(screen.getByText('Ana et 2 autres écrivent…')).toBeTruthy();
|
|
67
|
+
});
|
|
68
|
+
|
|
23
69
|
it('renders dots only when names is empty', () => {
|
|
24
70
|
render(<ChatTypingIndicator names={[]} data-testid="typing" />);
|
|
25
71
|
const root = screen.getByTestId('typing');
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file ChatTypingIndicator.tsx
|
|
7
|
-
* @input Uses React, StyleX (keyframes), theme tokens
|
|
7
|
+
* @input Uses React, i18n (useTranslator/useLocale), StyleX (keyframes), theme tokens
|
|
8
8
|
* @output Exports ChatTypingIndicator component and ChatTypingIndicatorProps
|
|
9
9
|
* @position Animated "X is typing…" hint above a chat composer
|
|
10
10
|
*
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* - /apps/storybook/stories/ChatAdditions.stories.tsx (examples)
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import {useMemo} from 'react';
|
|
22
23
|
import * as stylex from '@stylexjs/stylex';
|
|
23
24
|
import {
|
|
24
25
|
colorVars,
|
|
@@ -31,6 +32,8 @@ import {
|
|
|
31
32
|
import type {BaseProps} from '@astryxdesign/core';
|
|
32
33
|
import {mergeProps} from '@astryxdesign/core/utils';
|
|
33
34
|
import {themeProps} from '@astryxdesign/core/utils';
|
|
35
|
+
import {useLocale, useTranslator} from '@astryxdesign/core/i18n';
|
|
36
|
+
import type {TranslatorFn} from '@astryxdesign/core/i18n';
|
|
34
37
|
|
|
35
38
|
export interface ChatTypingIndicatorProps extends BaseProps<HTMLDivElement> {
|
|
36
39
|
/** Ref forwarded to the root element */
|
|
@@ -94,17 +97,36 @@ const styles = stylex.create({
|
|
|
94
97
|
},
|
|
95
98
|
});
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Builds the status sentence for the people currently typing.
|
|
102
|
+
*
|
|
103
|
+
* Every phrase comes from the translation catalog, and the names themselves are
|
|
104
|
+
* joined by `Intl.ListFormat` for the active locale rather than an English
|
|
105
|
+
* "and". Three or more people collapse to the first name plus a translated
|
|
106
|
+
* overflow phrase, which is then joined the same way — so the conjunction, the
|
|
107
|
+
* separator, and their order all follow the locale instead of this file.
|
|
108
|
+
*/
|
|
109
|
+
function typingLabel(
|
|
110
|
+
names: string[] | undefined,
|
|
111
|
+
t: TranslatorFn,
|
|
112
|
+
listFormat: Intl.ListFormat,
|
|
113
|
+
): string | null {
|
|
98
114
|
if (names == null || names.length === 0) {
|
|
99
115
|
return null;
|
|
100
116
|
}
|
|
101
117
|
if (names.length === 1) {
|
|
102
|
-
return
|
|
118
|
+
return t('@astryx.chatTypingIndicator.one', {name: names[0]});
|
|
103
119
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
120
|
+
const parts =
|
|
121
|
+
names.length === 2
|
|
122
|
+
? [names[0], names[1]]
|
|
123
|
+
: [
|
|
124
|
+
names[0],
|
|
125
|
+
t('@astryx.chatTypingIndicator.others', {count: names.length - 1}),
|
|
126
|
+
];
|
|
127
|
+
return t('@astryx.chatTypingIndicator.many', {
|
|
128
|
+
names: listFormat.format(parts),
|
|
129
|
+
});
|
|
108
130
|
}
|
|
109
131
|
|
|
110
132
|
// =============================================================================
|
|
@@ -132,7 +154,13 @@ export function ChatTypingIndicator({
|
|
|
132
154
|
'data-testid': testId,
|
|
133
155
|
ref,
|
|
134
156
|
}: ChatTypingIndicatorProps) {
|
|
135
|
-
const
|
|
157
|
+
const t = useTranslator();
|
|
158
|
+
const locale = useLocale();
|
|
159
|
+
const listFormat = useMemo(
|
|
160
|
+
() => new Intl.ListFormat(locale, {style: 'long', type: 'conjunction'}),
|
|
161
|
+
[locale],
|
|
162
|
+
);
|
|
163
|
+
const label = typingLabel(names, t, listFormat);
|
|
136
164
|
return (
|
|
137
165
|
<div
|
|
138
166
|
ref={ref}
|