@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.
- package/README.md +2 -0
- package/dist/Chat/ChatEmojiPicker.d.ts +80 -0
- package/dist/Chat/ChatEmojiPicker.d.ts.map +1 -0
- package/dist/Chat/ChatEmojiPicker.js +201 -0
- package/dist/Chat/ChatReactionBar.d.ts +73 -0
- package/dist/Chat/ChatReactionBar.d.ts.map +1 -0
- package/dist/Chat/ChatReactionBar.js +148 -0
- package/dist/Chat/ChatTypingIndicator.d.ts +30 -0
- package/dist/Chat/ChatTypingIndicator.d.ts.map +1 -0
- package/dist/Chat/ChatTypingIndicator.js +112 -0
- package/dist/Chat/ChatUnreadDivider.d.ts +27 -0
- package/dist/Chat/ChatUnreadDivider.d.ts.map +1 -0
- package/dist/Chat/ChatUnreadDivider.js +83 -0
- package/dist/Chat/index.d.ts +15 -0
- package/dist/Chat/index.d.ts.map +1 -0
- package/dist/Chat/index.js +14 -0
- package/dist/Drawer/Drawer.d.ts +135 -0
- package/dist/Drawer/Drawer.d.ts.map +1 -0
- package/dist/Drawer/Drawer.js +503 -0
- package/dist/Drawer/index.d.ts +9 -0
- package/dist/Drawer/index.d.ts.map +1 -0
- package/dist/Drawer/index.js +10 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/lab.css +118 -21
- package/package.json +2 -2
- package/src/Chat/ChatEmojiPicker.doc.mjs +68 -0
- package/src/Chat/ChatEmojiPicker.test.tsx +143 -0
- package/src/Chat/ChatEmojiPicker.tsx +287 -0
- package/src/Chat/ChatReactionBar.doc.mjs +73 -0
- package/src/Chat/ChatReactionBar.test.tsx +124 -0
- package/src/Chat/ChatReactionBar.tsx +290 -0
- package/src/Chat/ChatTypingIndicator.doc.mjs +35 -0
- package/src/Chat/ChatTypingIndicator.test.tsx +48 -0
- package/src/Chat/ChatTypingIndicator.tsx +158 -0
- package/src/Chat/ChatUnreadDivider.doc.mjs +36 -0
- package/src/Chat/ChatUnreadDivider.test.tsx +35 -0
- package/src/Chat/ChatUnreadDivider.tsx +113 -0
- package/src/Chat/index.ts +22 -0
- package/src/Drawer/Drawer.doc.mjs +157 -0
- package/src/Drawer/Drawer.test.tsx +636 -0
- package/src/Drawer/Drawer.tsx +741 -0
- package/src/Drawer/index.ts +11 -0
- 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';
|