@astryxdesign/lab 0.1.4-canary.7aef8d3 → 0.1.4-canary.7e5db0e

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.
Files changed (45) hide show
  1. package/README.md +2 -0
  2. package/dist/Chat/ChatEmojiPicker.d.ts +80 -0
  3. package/dist/Chat/ChatEmojiPicker.d.ts.map +1 -0
  4. package/dist/Chat/ChatEmojiPicker.js +201 -0
  5. package/dist/Chat/ChatReactionBar.d.ts +73 -0
  6. package/dist/Chat/ChatReactionBar.d.ts.map +1 -0
  7. package/dist/Chat/ChatReactionBar.js +148 -0
  8. package/dist/Chat/ChatTypingIndicator.d.ts +30 -0
  9. package/dist/Chat/ChatTypingIndicator.d.ts.map +1 -0
  10. package/dist/Chat/ChatTypingIndicator.js +112 -0
  11. package/dist/Chat/ChatUnreadDivider.d.ts +27 -0
  12. package/dist/Chat/ChatUnreadDivider.d.ts.map +1 -0
  13. package/dist/Chat/ChatUnreadDivider.js +83 -0
  14. package/dist/Chat/index.d.ts +15 -0
  15. package/dist/Chat/index.d.ts.map +1 -0
  16. package/dist/Chat/index.js +14 -0
  17. package/dist/Drawer/Drawer.d.ts +135 -0
  18. package/dist/Drawer/Drawer.d.ts.map +1 -0
  19. package/dist/Drawer/Drawer.js +503 -0
  20. package/dist/Drawer/index.d.ts +9 -0
  21. package/dist/Drawer/index.d.ts.map +1 -0
  22. package/dist/Drawer/index.js +10 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +4 -0
  26. package/dist/lab.css +118 -21
  27. package/package.json +2 -2
  28. package/src/Chat/ChatEmojiPicker.doc.mjs +68 -0
  29. package/src/Chat/ChatEmojiPicker.test.tsx +143 -0
  30. package/src/Chat/ChatEmojiPicker.tsx +287 -0
  31. package/src/Chat/ChatReactionBar.doc.mjs +73 -0
  32. package/src/Chat/ChatReactionBar.test.tsx +124 -0
  33. package/src/Chat/ChatReactionBar.tsx +290 -0
  34. package/src/Chat/ChatTypingIndicator.doc.mjs +35 -0
  35. package/src/Chat/ChatTypingIndicator.test.tsx +48 -0
  36. package/src/Chat/ChatTypingIndicator.tsx +158 -0
  37. package/src/Chat/ChatUnreadDivider.doc.mjs +36 -0
  38. package/src/Chat/ChatUnreadDivider.test.tsx +35 -0
  39. package/src/Chat/ChatUnreadDivider.tsx +113 -0
  40. package/src/Chat/index.ts +22 -0
  41. package/src/Drawer/Drawer.doc.mjs +157 -0
  42. package/src/Drawer/Drawer.test.tsx +636 -0
  43. package/src/Drawer/Drawer.tsx +741 -0
  44. package/src/Drawer/index.ts +11 -0
  45. package/src/index.ts +4 -0
package/README.md CHANGED
@@ -12,6 +12,8 @@ Components in lab:
12
12
  - Compose with `@astryxdesign/core` components (use the theme, follow naming conventions)
13
13
  - Graduate to `@astryxdesign/core` after thorough engineering review
14
14
 
15
+ See the **[Component Lifecycle](https://github.com/facebook/astryx/wiki/Component-Lifecycle)** wiki for how a component moves from lab → core (and the promotion gates), and the **[Component Hardening Protocol](https://github.com/facebook/astryx/wiki/Component-Hardening-Protocol)** for the bar a component must clear to graduate.
16
+
15
17
  ## What's here vs what's in core
16
18
 
17
19
  **Lab:** Works, has basic props, maybe has stories. API might change. Not accessibility-hardened, not vibe-tested, not fully themed. **Canary-only — no stability promise.**
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @file ChatEmojiPicker.tsx
3
+ * @input Uses React, StyleX, theme tokens, Popover, TextInput, useGridFocus
4
+ * @output Exports ChatEmojiPicker component, ChatEmojiPickerProps,
5
+ * ChatEmojiOption, and DEFAULT_CHAT_EMOJIS
6
+ * @position Popover emoji grid for chat reactions; wraps a trigger button
7
+ *
8
+ * Renders a Popover around a trigger element: a shortname filter input over
9
+ * a fixed 8-column emoji grid. Arrow keys move focus through the grid
10
+ * (left/right by one, up/down by a row) via useGridFocus; picking an emoji
11
+ * calls onSelect and closes the popover, which restores focus to the
12
+ * trigger. Ships with a small default emoji set; pass `emojis` to override.
13
+ *
14
+ * SYNC: When modified, update these files to stay in sync:
15
+ * - /packages/lab/src/Chat/index.ts (exports)
16
+ * - /packages/lab/src/Chat/ChatEmojiPicker.doc.mjs
17
+ * - /apps/storybook/stories/ChatAdditions.stories.tsx (examples)
18
+ */
19
+ import type { ReactNode } from 'react';
20
+ import type { BaseProps } from '@astryxdesign/core';
21
+ export interface ChatEmojiOption {
22
+ /** The emoji character, e.g. "🎉". */
23
+ emoji: string;
24
+ /** Shortname used for filtering and accessible labels, e.g. "tada". */
25
+ name: string;
26
+ }
27
+ /**
28
+ * Small default emoji set (two grid rows) covering the common
29
+ * reaction vocabulary. Override via the `emojis` prop.
30
+ */
31
+ export declare const DEFAULT_CHAT_EMOJIS: ReadonlyArray<ChatEmojiOption>;
32
+ export interface ChatEmojiPickerProps extends Omit<BaseProps<HTMLDivElement>, 'onSelect'> {
33
+ /** Ref forwarded to the popover panel element */
34
+ ref?: React.Ref<HTMLDivElement>;
35
+ /**
36
+ * Called with the picked emoji character.
37
+ * The popover closes itself after selection.
38
+ */
39
+ onSelect: (emoji: string) => void;
40
+ /**
41
+ * Emoji options rendered in the grid (8 per row).
42
+ * @default DEFAULT_CHAT_EMOJIS
43
+ */
44
+ emojis?: ReadonlyArray<ChatEmojiOption>;
45
+ /**
46
+ * Accessible label for the popover dialog.
47
+ * @default 'Pick an emoji'
48
+ */
49
+ label?: string;
50
+ /**
51
+ * Placeholder and hidden label for the filter input.
52
+ * @default 'Search emoji'
53
+ */
54
+ searchLabel?: string;
55
+ /**
56
+ * Trigger element — must contain a `<button>` (Popover wires it up).
57
+ */
58
+ children: ReactNode;
59
+ /** Test ID for the popover content. */
60
+ 'data-testid'?: string;
61
+ }
62
+ /**
63
+ * Popover emoji grid with shortname filtering and arrow-key navigation.
64
+ *
65
+ * Wraps its trigger button in a Popover. Typing in the filter input narrows
66
+ * the grid by shortname; arrow keys move focus between emoji; picking one
67
+ * calls `onSelect` and closes the popover (focus returns to the trigger).
68
+ *
69
+ * @example
70
+ * ```
71
+ * <ChatEmojiPicker onSelect={(emoji) => addReaction(emoji)}>
72
+ * <button type="button" aria-label="Add reaction">🙂</button>
73
+ * </ChatEmojiPicker>
74
+ * ```
75
+ */
76
+ export declare function ChatEmojiPicker({ onSelect, emojis, label, searchLabel, children, xstyle, className, style: styleProp, 'data-testid': testId, ref, }: ChatEmojiPickerProps): import("react").JSX.Element;
77
+ export declare namespace ChatEmojiPicker {
78
+ var displayName: string;
79
+ }
80
+ //# sourceMappingURL=ChatEmojiPicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatEmojiPicker.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatEmojiPicker.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAYrC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAOlD,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC,eAAe,CAiB9D,CAAC;AAIF,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAChD,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,iDAAiD;IACjD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;;OAGG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAExC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB,uCAAuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA+DD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,MAA4B,EAC5B,KAAuB,EACvB,WAA4B,EAC5B,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,GACJ,EAAE,oBAAoB,+BAoFtB;yBA/Fe,eAAe"}
@@ -0,0 +1,201 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file ChatEmojiPicker.tsx
7
+ * @input Uses React, StyleX, theme tokens, Popover, TextInput, useGridFocus
8
+ * @output Exports ChatEmojiPicker component, ChatEmojiPickerProps,
9
+ * ChatEmojiOption, and DEFAULT_CHAT_EMOJIS
10
+ * @position Popover emoji grid for chat reactions; wraps a trigger button
11
+ *
12
+ * Renders a Popover around a trigger element: a shortname filter input over
13
+ * a fixed 8-column emoji grid. Arrow keys move focus through the grid
14
+ * (left/right by one, up/down by a row) via useGridFocus; picking an emoji
15
+ * calls onSelect and closes the popover, which restores focus to the
16
+ * trigger. Ships with a small default emoji set; pass `emojis` to override.
17
+ *
18
+ * SYNC: When modified, update these files to stay in sync:
19
+ * - /packages/lab/src/Chat/index.ts (exports)
20
+ * - /packages/lab/src/Chat/ChatEmojiPicker.doc.mjs
21
+ * - /apps/storybook/stories/ChatAdditions.stories.tsx (examples)
22
+ */
23
+ import { useState } from 'react';
24
+ import * as stylex from '@stylexjs/stylex';
25
+ import '@astryxdesign/core/theme/tokens.stylex';
26
+ import { colorVars, spacingVars, radiusVars, durationVars, easeVars, typographyVars, typeScaleVars } from '@astryxdesign/core/theme/tokens.stylex';
27
+ import { mergeProps } from '@astryxdesign/core/utils';
28
+ import { themeProps } from '@astryxdesign/core/utils';
29
+ import { Popover } from '@astryxdesign/core/Popover';
30
+ import { TextInput } from '@astryxdesign/core/TextInput';
31
+ import { useGridFocus } from '@astryxdesign/core/hooks';
32
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
33
+ /**
34
+ * Small default emoji set (two grid rows) covering the common
35
+ * reaction vocabulary. Override via the `emojis` prop.
36
+ */
37
+ export const DEFAULT_CHAT_EMOJIS = [{
38
+ emoji: '👍',
39
+ name: 'thumbs up'
40
+ }, {
41
+ emoji: '❤️',
42
+ name: 'heart'
43
+ }, {
44
+ emoji: '😂',
45
+ name: 'joy'
46
+ }, {
47
+ emoji: '🎉',
48
+ name: 'tada'
49
+ }, {
50
+ emoji: '😮',
51
+ name: 'wow'
52
+ }, {
53
+ emoji: '😢',
54
+ name: 'cry'
55
+ }, {
56
+ emoji: '🔥',
57
+ name: 'fire'
58
+ }, {
59
+ emoji: '👀',
60
+ name: 'eyes'
61
+ }, {
62
+ emoji: '✅',
63
+ name: 'check'
64
+ }, {
65
+ emoji: '🙏',
66
+ name: 'pray'
67
+ }, {
68
+ emoji: '💯',
69
+ name: 'hundred'
70
+ }, {
71
+ emoji: '🚀',
72
+ name: 'rocket'
73
+ }, {
74
+ emoji: '😍',
75
+ name: 'heart eyes'
76
+ }, {
77
+ emoji: '🤔',
78
+ name: 'thinking'
79
+ }, {
80
+ emoji: '👋',
81
+ name: 'wave'
82
+ }, {
83
+ emoji: '⭐',
84
+ name: 'star'
85
+ }];
86
+ const GRID_COLUMNS = 8;
87
+ // =============================================================================
88
+ // Styles
89
+ // =============================================================================
90
+
91
+ const styles = {
92
+ panel: {
93
+ k1xSpc: "x78zum5",
94
+ kXwgrk: "xdt5ytf",
95
+ kOIVth: "x1txdalj",
96
+ kzqmXN: "x1dz1jew",
97
+ kB7OPa: "x9f619",
98
+ $$css: true
99
+ }
100
+ };
101
+
102
+ // =============================================================================
103
+ // Component
104
+ // =============================================================================
105
+
106
+ /**
107
+ * Popover emoji grid with shortname filtering and arrow-key navigation.
108
+ *
109
+ * Wraps its trigger button in a Popover. Typing in the filter input narrows
110
+ * the grid by shortname; arrow keys move focus between emoji; picking one
111
+ * calls `onSelect` and closes the popover (focus returns to the trigger).
112
+ *
113
+ * @example
114
+ * ```
115
+ * <ChatEmojiPicker onSelect={(emoji) => addReaction(emoji)}>
116
+ * <button type="button" aria-label="Add reaction">🙂</button>
117
+ * </ChatEmojiPicker>
118
+ * ```
119
+ */
120
+ export function ChatEmojiPicker({
121
+ onSelect,
122
+ emojis = DEFAULT_CHAT_EMOJIS,
123
+ label = 'Pick an emoji',
124
+ searchLabel = 'Search emoji',
125
+ children,
126
+ xstyle,
127
+ className,
128
+ style: styleProp,
129
+ 'data-testid': testId,
130
+ ref
131
+ }) {
132
+ const [isOpen, setIsOpen] = useState(false);
133
+ const [query, setQuery] = useState('');
134
+ const {
135
+ gridRef,
136
+ handleKeyDown
137
+ } = useGridFocus({
138
+ columns: GRID_COLUMNS
139
+ });
140
+ const trimmed = query.trim().toLowerCase();
141
+ const visible = trimmed === '' ? emojis : emojis.filter(option => option.name.toLowerCase().includes(trimmed) || option.emoji === trimmed);
142
+ const handleOpenChange = open => {
143
+ setIsOpen(open);
144
+ if (!open) {
145
+ setQuery('');
146
+ }
147
+ };
148
+ const handleSelect = emoji => {
149
+ onSelect(emoji);
150
+ handleOpenChange(false);
151
+ };
152
+ return /*#__PURE__*/_jsx(Popover, {
153
+ isOpen: isOpen,
154
+ onOpenChange: handleOpenChange,
155
+ label: label,
156
+ placement: "below",
157
+ alignment: "start",
158
+ "data-testid": testId,
159
+ content: /*#__PURE__*/_jsxs("div", {
160
+ ref: ref,
161
+ ...mergeProps(themeProps('chat-emoji-picker'), stylex.props(styles.panel, xstyle), className, styleProp),
162
+ children: [/*#__PURE__*/_jsx(TextInput, {
163
+ label: searchLabel,
164
+ isLabelHidden: true,
165
+ size: "sm",
166
+ placeholder: searchLabel,
167
+ value: query,
168
+ onChange: setQuery,
169
+ hasClear: true
170
+ }), visible.length === 0 ? /*#__PURE__*/_jsxs("div", {
171
+ ...{
172
+ className: "xce4md1 x7a5moj x9ynric x141an7d xv1l7n4"
173
+ },
174
+ children: ["No emoji match \u201C", query.trim(), "\u201D."]
175
+ }) : /*#__PURE__*/_jsx("div", {
176
+ ref: gridRef,
177
+ role: "group",
178
+ "aria-label": "Emoji",
179
+ onKeyDown: handleKeyDown,
180
+ ...{
181
+ className: "xrvj5dj x12m0uhf x1lsbc85"
182
+ },
183
+ children: visible.map(option => /*#__PURE__*/_jsx("button", {
184
+ type: "button",
185
+ "data-emoji": option.emoji,
186
+ "aria-label": `React with ${option.name}`,
187
+ onClick: () => handleSelect(option.emoji),
188
+ ...{
189
+ className: "x3nfvp2 x6s0dn4 xl56j7k x1td3qas x10w6t97 xx3sua9 xng3xce x1717udv xjbqb8w x1n5bzlp xe9uy6x x1ypdohk xosj86m xw6l6zx x1a2a7pz x17nn4n9 x2ssjo2 x15406qy xuedmi6 x12w9bfk xlr8y92"
190
+ },
191
+ children: /*#__PURE__*/_jsx("span", {
192
+ "aria-hidden": "true",
193
+ children: option.emoji
194
+ })
195
+ }, option.emoji))
196
+ })]
197
+ }),
198
+ children: children
199
+ });
200
+ }
201
+ ChatEmojiPicker.displayName = 'ChatEmojiPicker';
@@ -0,0 +1,73 @@
1
+ import type { BaseProps } from '@astryxdesign/core';
2
+ import type { ChatEmojiOption } from './ChatEmojiPicker';
3
+ export interface ChatReaction {
4
+ /** The emoji character, e.g. "🎉". */
5
+ emoji: string;
6
+ /** Number of people who reacted with this emoji. */
7
+ count: number;
8
+ /**
9
+ * Whether the current user has reacted — accent border, tinted
10
+ * background, and `aria-pressed="true"` on the pill.
11
+ * @default false
12
+ */
13
+ isSelected?: boolean;
14
+ /**
15
+ * Human-readable description used as the pill's tooltip and accessible
16
+ * label, e.g. "Ana and Dana reacted with 🎉". Falls back to a count-based
17
+ * label when omitted.
18
+ */
19
+ label?: string;
20
+ }
21
+ export interface ChatReactionBarProps extends Omit<BaseProps<HTMLDivElement>, 'onToggle'> {
22
+ /** Ref forwarded to the root element */
23
+ ref?: React.Ref<HTMLDivElement>;
24
+ /** Reactions to render, in display order. */
25
+ reactions: ChatReaction[];
26
+ /** Called with the pill's emoji when the user toggles a reaction. */
27
+ onToggle?: (emoji: string) => void;
28
+ /**
29
+ * Called with the picked emoji when the user adds a reaction from the
30
+ * emoji picker. The trailing add-reaction button renders only when
31
+ * this is provided.
32
+ */
33
+ onAdd?: (emoji: string) => void;
34
+ /**
35
+ * Emoji options for the add-reaction picker.
36
+ * @default DEFAULT_CHAT_EMOJIS
37
+ */
38
+ emojis?: ReadonlyArray<ChatEmojiOption>;
39
+ /**
40
+ * Accessible label for the add-reaction button.
41
+ * @default 'Add reaction'
42
+ */
43
+ addLabel?: string;
44
+ /**
45
+ * Accessible label for the reaction group.
46
+ * @default 'Reactions'
47
+ */
48
+ label?: string;
49
+ }
50
+ /**
51
+ * Row of emoji reaction pills under a chat message.
52
+ *
53
+ * Each pill shows an emoji and count; the current user's own reactions get
54
+ * an accent tint and `aria-pressed`. Provide `onAdd` to render a trailing
55
+ * add-reaction button that opens a ChatEmojiPicker popover.
56
+ *
57
+ * @example
58
+ * ```
59
+ * <ChatReactionBar
60
+ * reactions={[
61
+ * {emoji: '🎉', count: 4, isSelected: true, label: 'You and Dana reacted with 🎉'},
62
+ * {emoji: '👀', count: 2},
63
+ * ]}
64
+ * onToggle={(emoji) => toggleReaction(emoji)}
65
+ * onAdd={(emoji) => addReaction(emoji)}
66
+ * />
67
+ * ```
68
+ */
69
+ export declare function ChatReactionBar({ reactions, onToggle, onAdd, emojis, addLabel, label, xstyle, className, style: styleProp, 'data-testid': testId, ref, }: ChatReactionBarProps): import("react").JSX.Element;
70
+ export declare namespace ChatReactionBar {
71
+ var displayName: string;
72
+ }
73
+ //# sourceMappingURL=ChatReactionBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatReactionBar.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatReactionBar.tsx"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAKlD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAChD,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC,6CAA6C;IAC7C,SAAS,EAAE,YAAY,EAAE,CAAC;IAE1B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEnC;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhC;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAExC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAuGD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,QAAQ,EACR,KAAK,EACL,MAAM,EACN,QAAyB,EACzB,KAAmB,EACnB,MAAM,EACN,SAAS,EACT,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,GACJ,EAAE,oBAAoB,+BAyDtB;yBArEe,eAAe"}
@@ -0,0 +1,148 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file ChatReactionBar.tsx
7
+ * @input Uses React, StyleX, theme tokens, Tooltip, ChatEmojiPicker
8
+ * @output Exports ChatReactionBar component, ChatReactionBarProps, ChatReaction
9
+ * @position Row of emoji reaction pills under a chat message
10
+ *
11
+ * Renders reaction pills (emoji + count) with a pressed state for the
12
+ * current user's own reactions, optional tooltips listing reactors, and an
13
+ * optional trailing add-reaction button that opens a ChatEmojiPicker
14
+ * popover. Data flows in via `reactions`; toggles flow out via `onToggle`
15
+ * and new reactions via `onAdd`.
16
+ *
17
+ * SYNC: When modified, update these files to stay in sync:
18
+ * - /packages/lab/src/Chat/index.ts (exports)
19
+ * - /packages/lab/src/Chat/ChatReactionBar.doc.mjs
20
+ * - /apps/storybook/stories/ChatAdditions.stories.tsx (examples)
21
+ */
22
+ import * as stylex from '@stylexjs/stylex';
23
+ import '@astryxdesign/core/theme/tokens.stylex';
24
+ import { colorVars, spacingVars, radiusVars, durationVars, easeVars, typographyVars, typeScaleVars, fontWeightVars } from '@astryxdesign/core/theme/tokens.stylex';
25
+ import { mergeProps } from '@astryxdesign/core/utils';
26
+ import { themeProps } from '@astryxdesign/core/utils';
27
+ import { Tooltip } from '@astryxdesign/core/Tooltip';
28
+ import { ChatEmojiPicker } from "./ChatEmojiPicker.js";
29
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
30
+ // =============================================================================
31
+ // Styles
32
+ // =============================================================================
33
+
34
+ const styles = {
35
+ root: {
36
+ k1xSpc: "x78zum5",
37
+ kwnvtZ: "x1a02dak",
38
+ kGNEyG: "x6s0dn4",
39
+ kOIVth: "xzye2dw",
40
+ $$css: true
41
+ }
42
+ };
43
+ function pillLabel(reaction) {
44
+ if (reaction.label != null) {
45
+ return reaction.label;
46
+ }
47
+ const noun = reaction.count === 1 ? 'reaction' : 'reactions';
48
+ return `${reaction.count} ${noun} with ${reaction.emoji}`;
49
+ }
50
+
51
+ // =============================================================================
52
+ // Component
53
+ // =============================================================================
54
+
55
+ /**
56
+ * Row of emoji reaction pills under a chat message.
57
+ *
58
+ * Each pill shows an emoji and count; the current user's own reactions get
59
+ * an accent tint and `aria-pressed`. Provide `onAdd` to render a trailing
60
+ * add-reaction button that opens a ChatEmojiPicker popover.
61
+ *
62
+ * @example
63
+ * ```
64
+ * <ChatReactionBar
65
+ * reactions={[
66
+ * {emoji: '🎉', count: 4, isSelected: true, label: 'You and Dana reacted with 🎉'},
67
+ * {emoji: '👀', count: 2},
68
+ * ]}
69
+ * onToggle={(emoji) => toggleReaction(emoji)}
70
+ * onAdd={(emoji) => addReaction(emoji)}
71
+ * />
72
+ * ```
73
+ */
74
+ export function ChatReactionBar({
75
+ reactions,
76
+ onToggle,
77
+ onAdd,
78
+ emojis,
79
+ addLabel = 'Add reaction',
80
+ label = 'Reactions',
81
+ xstyle,
82
+ className,
83
+ style: styleProp,
84
+ 'data-testid': testId,
85
+ ref
86
+ }) {
87
+ return /*#__PURE__*/_jsxs("div", {
88
+ ref: ref,
89
+ role: "group",
90
+ "aria-label": label,
91
+ "data-testid": testId,
92
+ ...mergeProps(themeProps('chat-reaction-bar'), stylex.props(styles.root, xstyle), className, styleProp),
93
+ children: [reactions.map(reaction => {
94
+ const isSelected = reaction.isSelected === true;
95
+ const pill = /*#__PURE__*/_jsxs("button", {
96
+ type: "button",
97
+ "aria-pressed": isSelected,
98
+ "aria-label": pillLabel(reaction),
99
+ onClick: () => onToggle?.(reaction.emoji),
100
+ ...{
101
+ 0: {
102
+ className: "x3nfvp2 x6s0dn4 xzye2dw xxk0z11 xf314gf xjspbzw xmkeg23 x1y0btm7 x9r1u3d x1xfd4ba x1ww4t2b xwmxj5m x1ypdohk x9ynric x9f619 xq90yva xuedmi6 x12w9bfk xlr8y92"
103
+ },
104
+ 1: {
105
+ className: "x3nfvp2 x6s0dn4 xzye2dw xxk0z11 xf314gf xjspbzw xmkeg23 x1y0btm7 x1ypdohk x9ynric x9f619 xq90yva xuedmi6 x12w9bfk xlr8y92 xgcxg3y xad5do xn21ew4 xhggfp0"
106
+ }
107
+ }[!!isSelected << 0],
108
+ children: [/*#__PURE__*/_jsx("span", {
109
+ "aria-hidden": "true",
110
+ ...{
111
+ className: "xjm74w1 xw6l6zx"
112
+ },
113
+ children: reaction.emoji
114
+ }), /*#__PURE__*/_jsx("span", {
115
+ ...{
116
+ 0: {
117
+ className: "x141an7d x1e4wzip xv1l7n4 xss6m8b x1ltkj2j"
118
+ },
119
+ 1: {
120
+ className: "x141an7d xss6m8b x1ltkj2j xjse4m1 x2mo6ok"
121
+ }
122
+ }[!!isSelected << 0],
123
+ children: reaction.count
124
+ })]
125
+ }, reaction.emoji);
126
+ return reaction.label != null ? /*#__PURE__*/_jsx(Tooltip, {
127
+ content: reaction.label,
128
+ children: pill
129
+ }, reaction.emoji) : pill;
130
+ }), onAdd != null && /*#__PURE__*/_jsx(ChatEmojiPicker, {
131
+ emojis: emojis,
132
+ onSelect: onAdd,
133
+ label: addLabel,
134
+ children: /*#__PURE__*/_jsx(Tooltip, {
135
+ content: addLabel,
136
+ children: /*#__PURE__*/_jsx("button", {
137
+ type: "button",
138
+ "aria-label": addLabel,
139
+ ...{
140
+ className: "x3nfvp2 x6s0dn4 xl56j7k x1td3qas xxk0z11 xjspbzw xmkeg23 x1y0btm7 x9r1u3d x1xfd4ba x1ww4t2b xwmxj5m xv9yike x1ypdohk x9f619 x1jvydc1 x1ltkj2j x1717udv"
141
+ },
142
+ children: "+"
143
+ })
144
+ })
145
+ })]
146
+ });
147
+ }
148
+ ChatReactionBar.displayName = 'ChatReactionBar';
@@ -0,0 +1,30 @@
1
+ import type { BaseProps } from '@astryxdesign/core';
2
+ export interface ChatTypingIndicatorProps extends BaseProps<HTMLDivElement> {
3
+ /** Ref forwarded to the root element */
4
+ ref?: React.Ref<HTMLDivElement>;
5
+ /**
6
+ * Names of people currently typing. Drives the label:
7
+ * one name → "Ana is typing…", two → "Ana and Ben are typing…",
8
+ * more → "Ana and 2 others are typing…". When omitted or empty,
9
+ * only the animated dots render.
10
+ */
11
+ names?: string[];
12
+ }
13
+ /**
14
+ * Animated three-dot typing hint with a name-aware label.
15
+ *
16
+ * The dots bounce with staggered delays (disabled under
17
+ * prefers-reduced-motion) and the label is announced politely to
18
+ * screen readers via role="status".
19
+ *
20
+ * @example
21
+ * ```
22
+ * <ChatTypingIndicator names={['Ana']} />
23
+ * <ChatTypingIndicator names={['Ana', 'Ben', 'Casey']} />
24
+ * ```
25
+ */
26
+ export declare function ChatTypingIndicator({ names, xstyle, className, style: styleProp, 'data-testid': testId, ref, }: ChatTypingIndicatorProps): import("react").JSX.Element;
27
+ export declare namespace ChatTypingIndicator {
28
+ var displayName: string;
29
+ }
30
+ //# sourceMappingURL=ChatTypingIndicator.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,112 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file ChatTypingIndicator.tsx
7
+ * @input Uses React, StyleX (keyframes), theme tokens
8
+ * @output Exports ChatTypingIndicator component and ChatTypingIndicatorProps
9
+ * @position Animated "X is typing…" hint above a chat composer
10
+ *
11
+ * Three staggered bouncing dots (stylex.keyframes, reduced-motion safe)
12
+ * with a grammar-aware label: "Ana is typing…", "Ana and Ben are typing…",
13
+ * or "Ana and 2 others are typing…". Announced politely via role="status".
14
+ * Renders dots only when no names are provided.
15
+ *
16
+ * SYNC: When modified, update these files to stay in sync:
17
+ * - /packages/lab/src/Chat/index.ts (exports)
18
+ * - /packages/lab/src/Chat/ChatTypingIndicator.doc.mjs
19
+ * - /apps/storybook/stories/ChatAdditions.stories.tsx (examples)
20
+ */
21
+ import * as stylex from '@stylexjs/stylex';
22
+ import '@astryxdesign/core/theme/tokens.stylex';
23
+ import { colorVars, spacingVars, radiusVars, durationVars, typographyVars, typeScaleVars } from '@astryxdesign/core/theme/tokens.stylex';
24
+ import { mergeProps } from '@astryxdesign/core/utils';
25
+ import { themeProps } from '@astryxdesign/core/utils';
26
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
27
+ // =============================================================================
28
+ // Styles
29
+ // =============================================================================
30
+
31
+ const bounceKeyframes = "x17t2jll-B";
32
+ const styles = {
33
+ root: {
34
+ k1xSpc: "x78zum5",
35
+ kGNEyG: "x6s0dn4",
36
+ kOIVth: "x1txdalj",
37
+ kAzted: "xisnujt",
38
+ $$css: true
39
+ }
40
+ };
41
+ function typingLabel(names) {
42
+ if (names == null || names.length === 0) {
43
+ return null;
44
+ }
45
+ 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…`;
50
+ }
51
+ return `${names[0]} and ${names.length - 1} others are typing…`;
52
+ }
53
+
54
+ // =============================================================================
55
+ // Component
56
+ // =============================================================================
57
+
58
+ /**
59
+ * Animated three-dot typing hint with a name-aware label.
60
+ *
61
+ * The dots bounce with staggered delays (disabled under
62
+ * prefers-reduced-motion) and the label is announced politely to
63
+ * screen readers via role="status".
64
+ *
65
+ * @example
66
+ * ```
67
+ * <ChatTypingIndicator names={['Ana']} />
68
+ * <ChatTypingIndicator names={['Ana', 'Ben', 'Casey']} />
69
+ * ```
70
+ */
71
+ export function ChatTypingIndicator({
72
+ names,
73
+ xstyle,
74
+ className,
75
+ style: styleProp,
76
+ 'data-testid': testId,
77
+ ref
78
+ }) {
79
+ const label = typingLabel(names);
80
+ return /*#__PURE__*/_jsxs("div", {
81
+ ref: ref,
82
+ role: "status",
83
+ "aria-live": "polite",
84
+ "data-testid": testId,
85
+ ...mergeProps(themeProps('chat-typing-indicator'), stylex.props(styles.root, xstyle), className, styleProp),
86
+ children: [/*#__PURE__*/_jsxs("span", {
87
+ "aria-hidden": "true",
88
+ ...{
89
+ className: "x3nfvp2 x6s0dn4 x1lsbc85"
90
+ },
91
+ children: [/*#__PURE__*/_jsx("span", {
92
+ ...{
93
+ className: "x1ftt334 x1ycjhwn xjspbzw x1q5y3ey x17u5aw1 xxprybm xa4qsjk x4hg4is x1aquc0h"
94
+ }
95
+ }), /*#__PURE__*/_jsx("span", {
96
+ ...{
97
+ className: "x1ftt334 x1ycjhwn xjspbzw x1q5y3ey x17u5aw1 xxprybm xa4qsjk x4hg4is x1aquc0h x1jksiw5"
98
+ }
99
+ }), /*#__PURE__*/_jsx("span", {
100
+ ...{
101
+ className: "x1ftt334 x1ycjhwn xjspbzw x1q5y3ey x17u5aw1 xxprybm xa4qsjk x4hg4is x1aquc0h xtmkasn"
102
+ }
103
+ })]
104
+ }), label != null && /*#__PURE__*/_jsx("span", {
105
+ ...{
106
+ className: "x9ynric x141an7d xv1l7n4"
107
+ },
108
+ children: label
109
+ })]
110
+ });
111
+ }
112
+ ChatTypingIndicator.displayName = 'ChatTypingIndicator';