@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":"AA8BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAIlD,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;AAoED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,MAAM,EACN,SAAS,EACT,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,GACJ,EAAE,wBAAwB,+BAsB1B;yBA7Be,mBAAmB"}
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
- function typingLabel(names) {
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 `${names[0]} is typing…`;
47
- }
48
- if (names.length === 2) {
49
- return `${names[0]} and ${names[1]} are typing…`;
58
+ return t('@astryx.chatTypingIndicator.one', {
59
+ name: names[0]
60
+ });
50
61
  }
51
- return `${names[0]} and ${names.length - 1} others are typing…`;
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 label = typingLabel(names);
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.1-canary.f48d468",
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.1-canary.f48d468",
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.1-canary.f48d468",
60
- "@astryxdesign/cli": "0.6.1-canary.f48d468",
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: "string[]; 1='X is typing', 2='X and Y', 3+='X and N others'; empty=dots only",
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
- function typingLabel(names: string[] | undefined): string | null {
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 `${names[0]} is typing…`;
118
+ return t('@astryx.chatTypingIndicator.one', {name: names[0]});
103
119
  }
104
- if (names.length === 2) {
105
- return `${names[0]} and ${names[1]} are typing…`;
106
- }
107
- return `${names[0]} and ${names.length - 1} others are typing…`;
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 label = typingLabel(names);
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}