@mitodl/smoot-design 6.1.0 → 6.2.0

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.
@@ -75,13 +75,13 @@ const FlashcardsScreen = ({ flashcards, wasKeyboardFocus, }) => {
75
75
  return (React.createElement(Container, { ref: containerRef },
76
76
  React.createElement(Flashcard, { ref: flashcardRef, content: flashcards[cardIndex], "aria-label": `Flashcard ${cardIndex + 1} of ${flashcards.length}` }),
77
77
  React.createElement(Navigation, null,
78
- React.createElement(ActionButton_1.ActionButton, { onClick: () => setCardIndex(cardIndex - 1), disabled: cardIndex === 0, variant: "secondary", color: "secondary", size: "small" },
78
+ React.createElement(ActionButton_1.ActionButton, { onClick: () => setCardIndex(cardIndex - 1), disabled: cardIndex === 0, variant: "secondary", color: "secondary", size: "small", "aria-label": "Previous card" },
79
79
  React.createElement(react_2.RiArrowLeftLine, { "aria-hidden": true })),
80
80
  React.createElement(Page, null,
81
81
  cardIndex + 1,
82
82
  " / ",
83
83
  flashcards.length),
84
- React.createElement(ActionButton_1.ActionButton, { onClick: () => setCardIndex(cardIndex + 1), disabled: cardIndex === flashcards.length - 1, variant: "secondary", color: "secondary", size: "small" },
84
+ React.createElement(ActionButton_1.ActionButton, { onClick: () => setCardIndex(cardIndex + 1), disabled: cardIndex === flashcards.length - 1, variant: "secondary", color: "secondary", size: "small", "aria-label": "Next card" },
85
85
  React.createElement(react_2.RiArrowRightLine, { "aria-hidden": true })))));
86
86
  };
87
87
  exports.FlashcardsScreen = FlashcardsScreen;
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.RemoteTutorDrawer = void 0;
13
+ // @format
13
14
  const React = require("react");
14
15
  const react_1 = require("react");
15
16
  const styled_1 = require("@emotion/styled");
@@ -215,26 +216,28 @@ const RemoteTutorDrawer = ({ messageOrigin, transformBody = identity, className,
215
216
  };
216
217
  }, [messageOrigin, target]);
217
218
  (0, react_1.useEffect)(() => {
218
- scrollElement === null || scrollElement === void 0 ? void 0 : scrollElement.scrollTo({
219
+ var _a;
220
+ (_a = scrollElement === null || scrollElement === void 0 ? void 0 : scrollElement.scrollTo) === null || _a === void 0 ? void 0 : _a.call(scrollElement, {
219
221
  top: tab === "chat" ? scrollElement.scrollHeight : 0,
220
222
  });
221
223
  }, [tab, scrollElement]);
222
224
  const conversationStarters = (0, react_1.useMemo)(() => {
225
+ var _a;
223
226
  if (!payload)
224
227
  return [];
225
228
  return (payload.chat.conversationStarters ||
226
- ((response === null || response === void 0 ? void 0 : response.flashcards)
229
+ (((_a = response === null || response === void 0 ? void 0 : response.flashcards) === null || _a === void 0 ? void 0 : _a.length) && response.flashcards.length >= 3
227
230
  ? randomItems(response.flashcards, 3).map((flashcard) => ({
228
231
  content: flashcard.question,
229
232
  }))
230
233
  : DEFAULT_VIDEO_STARTERS));
231
234
  }, [payload, response]);
232
235
  if (!payload) {
233
- return null;
236
+ return React.createElement("div", { "data-testid": "remote-tutor-drawer-waiting" });
234
237
  }
235
238
  const { blockType, chat } = payload;
236
239
  const hasTabs = blockType === "video";
237
- return (React.createElement(Drawer_1.default, { className: className, PaperProps: {
240
+ return (React.createElement(Drawer_1.default, { "data-testid": "remote-tutor-drawer", className: className, PaperProps: {
238
241
  ref: paperRefCallback,
239
242
  sx: {
240
243
  width: "900px",
@@ -249,7 +252,7 @@ const RemoteTutorDrawer = ({ messageOrigin, transformBody = identity, className,
249
252
  React.createElement(Header, null,
250
253
  React.createElement(Title, null,
251
254
  payload.title ? React.createElement(react_2.RiSparkling2Line, null) : null,
252
- React.createElement(Typography_1.default, { variant: "body1" }, ((_b = payload.title) === null || _b === void 0 ? void 0 : _b.includes("AskTIM")) ? (React.createElement(React.Fragment, null,
255
+ React.createElement(Typography_1.default, { variant: "body1", component: "h2" }, ((_b = payload.title) === null || _b === void 0 ? void 0 : _b.includes("AskTIM")) ? (React.createElement(React.Fragment, null,
253
256
  "Ask",
254
257
  React.createElement("strong", null, "TIM"),
255
258
  payload.title.replace("AskTIM", ""))) : (payload.title))),
@@ -54,21 +54,8 @@ const IFrame = ({ payload }) => {
54
54
  };
55
55
  const meta = {
56
56
  title: "smoot-design/AI/RemoteTutorDrawer",
57
- render: ({ target }, { parameters: { blockType, chat } }) => (React.createElement(React.Fragment, null,
58
- blockType === "problem" ? (React.createElement(IFrame, { payload: {
59
- blockType,
60
- target,
61
- title: "AskTIM for help with Problem: Derivatives 1.1",
62
- chat: Object.assign({ apiUrl: TEST_API_STREAMING, initialMessages: INITIAL_MESSAGES, conversationStarters: STARTERS }, chat),
63
- } })) : null,
64
- blockType === "video" ? (React.createElement(IFrame, { payload: {
65
- blockType,
66
- target,
67
- chat: Object.assign({ apiUrl: TEST_API_STREAMING, initialMessages: INITIAL_MESSAGES, conversationStarters: STARTERS }, chat),
68
- summary: {
69
- apiUrl: CONTENT_FILE_URL,
70
- },
71
- } })) : null,
57
+ render: ({ target }, { parameters: { payload } }) => (React.createElement(React.Fragment, null,
58
+ React.createElement(IFrame, { payload: payload }),
72
59
  React.createElement(RemoteTutorDrawer_1.RemoteTutorDrawer, { target: target, messageOrigin: "http://localhost:6006" }))),
73
60
  };
74
61
  exports.default = meta;
@@ -77,9 +64,15 @@ exports.ProblemStory = {
77
64
  target: "problem-frame",
78
65
  },
79
66
  parameters: {
80
- blockType: "problem",
81
- chat: {
82
- conversationStarters: STARTERS,
67
+ payload: {
68
+ blockType: "problem",
69
+ target: "problem-frame",
70
+ title: "AskTIM for help with Problem: Derivatives 1.1",
71
+ chat: {
72
+ apiUrl: TEST_API_STREAMING,
73
+ initialMessages: INITIAL_MESSAGES,
74
+ conversationStarters: STARTERS,
75
+ },
83
76
  },
84
77
  },
85
78
  };
@@ -88,11 +81,16 @@ exports.EntryScreenStory = {
88
81
  target: "entry-screen-frame",
89
82
  },
90
83
  parameters: {
91
- blockType: "problem",
92
- chat: {
93
- conversationStarters: STARTERS,
94
- entryScreenEnabled: true,
95
- entryScreenTitle: "AskTIM about this problem",
84
+ payload: {
85
+ blockType: "problem",
86
+ target: "entry-screen-frame",
87
+ chat: {
88
+ apiUrl: TEST_API_STREAMING,
89
+ initialMessages: INITIAL_MESSAGES,
90
+ conversationStarters: STARTERS,
91
+ entryScreenEnabled: true,
92
+ entryScreenTitle: "AskTIM about this problem",
93
+ },
96
94
  },
97
95
  },
98
96
  };
@@ -104,9 +102,16 @@ exports.VideoStory = {
104
102
  target: "video-frame",
105
103
  },
106
104
  parameters: {
107
- blockType: "video",
108
- chat: {
109
- conversationStarters: STARTERS,
105
+ payload: {
106
+ blockType: "video",
107
+ target: "video-frame",
108
+ chat: {
109
+ apiUrl: TEST_API_STREAMING,
110
+ conversationStarters: STARTERS,
111
+ },
112
+ summary: {
113
+ apiUrl: CONTENT_FILE_URL,
114
+ },
110
115
  },
111
116
  msw: {
112
117
  handlers: [
@@ -127,10 +132,15 @@ exports.FlashcardConversationStartersStory = {
127
132
  target: "starters-frame",
128
133
  },
129
134
  parameters: {
130
- blockType: "video",
131
- chat: {
132
- conversationStarters: undefined,
133
- initialMessages: undefined,
135
+ payload: {
136
+ blockType: "video",
137
+ target: "starters-frame",
138
+ chat: {
139
+ apiUrl: TEST_API_STREAMING,
140
+ },
141
+ summary: {
142
+ apiUrl: CONTENT_FILE_URL,
143
+ },
134
144
  },
135
145
  msw: {
136
146
  handlers: [
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const react_1 = require("@testing-library/react");
13
+ const user_event_1 = require("@testing-library/user-event");
14
+ const RemoteTutorDrawer_1 = require("./RemoteTutorDrawer");
15
+ const ThemeProvider_1 = require("../../components/ThemeProvider/ThemeProvider");
16
+ const React = require("react");
17
+ const msw_1 = require("msw");
18
+ const node_1 = require("msw/node");
19
+ const TEST_API_STREAMING = "http://localhost:4567/test";
20
+ const CONTENT_FILE_URL = "http://localhost:4567/api/v1/contentfiles/1";
21
+ const CONTENT_RESPONSE = {
22
+ count: 1,
23
+ next: null,
24
+ previous: null,
25
+ results: [
26
+ {
27
+ id: 1,
28
+ summary: "This is a test summary",
29
+ flashcards: [
30
+ {
31
+ question: "Test question 1?",
32
+ answer: "Test answer 1",
33
+ },
34
+ {
35
+ question: "Test question 2?",
36
+ answer: "Test answer 2",
37
+ },
38
+ {
39
+ question: "Test question 3?",
40
+ answer: "Test answer 3",
41
+ },
42
+ ],
43
+ },
44
+ ],
45
+ };
46
+ class MockResizeObserver {
47
+ constructor() {
48
+ this.observe = jest.fn();
49
+ this.unobserve = jest.fn();
50
+ this.disconnect = jest.fn();
51
+ }
52
+ }
53
+ global.ResizeObserver = MockResizeObserver;
54
+ describe("RemoteTutorDrawer", () => {
55
+ const server = (0, node_1.setupServer)(msw_1.http.post(TEST_API_STREAMING, () => __awaiter(void 0, void 0, void 0, function* () {
56
+ return msw_1.HttpResponse.text("AI Response");
57
+ })), msw_1.http.get(CONTENT_FILE_URL, () => {
58
+ return msw_1.HttpResponse.json(CONTENT_RESPONSE);
59
+ }));
60
+ beforeEach(() => {
61
+ jest.resetAllMocks();
62
+ });
63
+ afterEach(() => {
64
+ server.resetHandlers();
65
+ });
66
+ afterAll(() => server.close());
67
+ const setup = (message) => __awaiter(void 0, void 0, void 0, function* () {
68
+ server.listen();
69
+ (0, react_1.render)(React.createElement(RemoteTutorDrawer_1.RemoteTutorDrawer, { "data-testid": "remote-tutor-drawer", messageOrigin: "http://localhost:6006", target: "ai-chat" }), { wrapper: ThemeProvider_1.ThemeProvider });
70
+ yield react_1.screen.findByTestId("remote-tutor-drawer-waiting");
71
+ const event = new MessageEvent("message", {
72
+ data: message,
73
+ origin: "http://localhost:6006",
74
+ });
75
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
76
+ window.dispatchEvent(event);
77
+ yield new Promise((resolve) => setTimeout(resolve, 100));
78
+ }));
79
+ });
80
+ test("Problem drawer opens showing title", () => __awaiter(void 0, void 0, void 0, function* () {
81
+ yield setup({
82
+ type: "smoot-design::tutor-drawer-open",
83
+ payload: {
84
+ blockType: "problem",
85
+ target: "ai-chat",
86
+ title: "Drawer Title",
87
+ chat: {
88
+ apiUrl: TEST_API_STREAMING,
89
+ },
90
+ },
91
+ });
92
+ react_1.screen.getByRole("heading", { name: "Drawer Title" });
93
+ }));
94
+ test("Video drawer opens showing chat entry screen and tabs", () => __awaiter(void 0, void 0, void 0, function* () {
95
+ yield setup({
96
+ type: "smoot-design::tutor-drawer-open",
97
+ payload: {
98
+ blockType: "video",
99
+ target: "ai-chat",
100
+ chat: {
101
+ entryScreenTitle: "Entry screen title",
102
+ apiUrl: TEST_API_STREAMING,
103
+ conversationStarters: [
104
+ { content: "Prompt 1" },
105
+ { content: "Prompt 2" },
106
+ { content: "Prompt 3" },
107
+ ],
108
+ },
109
+ summary: {
110
+ apiUrl: CONTENT_FILE_URL,
111
+ },
112
+ },
113
+ });
114
+ react_1.screen.getByText("Entry screen title");
115
+ react_1.screen.getByRole("tab", { name: "Chat" });
116
+ react_1.screen.getByRole("tab", { name: "Flashcards" });
117
+ react_1.screen.getByRole("tab", { name: "Summary" });
118
+ react_1.screen.getByRole("button", { name: "Prompt 1" });
119
+ react_1.screen.getByRole("button", { name: "Prompt 2" });
120
+ react_1.screen.getByRole("button", { name: "Prompt 3" });
121
+ }));
122
+ test("Video drawer chat entry screen selects starters from flashcards", () => __awaiter(void 0, void 0, void 0, function* () {
123
+ yield setup({
124
+ type: "smoot-design::tutor-drawer-open",
125
+ payload: {
126
+ blockType: "video",
127
+ target: "ai-chat",
128
+ chat: {
129
+ entryScreenTitle: "Entry screen title",
130
+ apiUrl: TEST_API_STREAMING,
131
+ },
132
+ summary: {
133
+ apiUrl: CONTENT_FILE_URL,
134
+ },
135
+ },
136
+ });
137
+ react_1.screen.getByRole("button", { name: "Test question 1?" });
138
+ react_1.screen.getByRole("button", { name: "Test question 2?" });
139
+ react_1.screen.getByRole("button", { name: "Test question 3?" });
140
+ }));
141
+ test("Video drawer chat entry screen shows default starters where no flashcards are available", server.boundary(() => __awaiter(void 0, void 0, void 0, function* () {
142
+ const contentResponse = JSON.parse(JSON.stringify(CONTENT_RESPONSE));
143
+ contentResponse.results[0].flashcards = null;
144
+ server.use(msw_1.http.get(CONTENT_FILE_URL, () => {
145
+ return msw_1.HttpResponse.json(contentResponse);
146
+ }));
147
+ yield setup({
148
+ type: "smoot-design::tutor-drawer-open",
149
+ payload: {
150
+ blockType: "video",
151
+ target: "ai-chat",
152
+ chat: {
153
+ entryScreenTitle: "Entry screen title",
154
+ apiUrl: TEST_API_STREAMING,
155
+ },
156
+ summary: {
157
+ apiUrl: CONTENT_FILE_URL,
158
+ },
159
+ },
160
+ });
161
+ react_1.screen.getByRole("button", {
162
+ name: "What are the most important concepts introduced in the video?",
163
+ });
164
+ react_1.screen.getByRole("button", {
165
+ name: "What examples are used to illustrate concepts covered in the video?",
166
+ });
167
+ react_1.screen.getByRole("button", {
168
+ name: "What are the key terms introduced in this video?",
169
+ });
170
+ })));
171
+ test("Video drawer chat entry screen displays default title", () => __awaiter(void 0, void 0, void 0, function* () {
172
+ yield setup({
173
+ type: "smoot-design::tutor-drawer-open",
174
+ payload: {
175
+ blockType: "video",
176
+ target: "ai-chat",
177
+ chat: {
178
+ apiUrl: TEST_API_STREAMING,
179
+ },
180
+ summary: {
181
+ apiUrl: CONTENT_FILE_URL,
182
+ },
183
+ },
184
+ });
185
+ react_1.screen.getByText("What do you want to know about this video?");
186
+ }));
187
+ test("Flashcard shows content and can be click navigated", server.boundary(() => __awaiter(void 0, void 0, void 0, function* () {
188
+ yield setup({
189
+ type: "smoot-design::tutor-drawer-open",
190
+ payload: {
191
+ blockType: "video",
192
+ target: "ai-chat",
193
+ chat: {
194
+ apiUrl: TEST_API_STREAMING,
195
+ },
196
+ summary: {
197
+ apiUrl: CONTENT_FILE_URL,
198
+ },
199
+ },
200
+ });
201
+ yield user_event_1.default.click(react_1.screen.getByRole("tab", { name: "Flashcards" }));
202
+ yield user_event_1.default.click(react_1.screen.getByText("Q: Test question 1?"));
203
+ react_1.screen.getByText("Answer: Test answer 1");
204
+ yield user_event_1.default.click(react_1.screen.getByRole("button", { name: "Next card" }));
205
+ yield user_event_1.default.click(react_1.screen.getByText("Q: Test question 2?"));
206
+ react_1.screen.getByText("Answer: Test answer 2");
207
+ yield user_event_1.default.click(react_1.screen.getByRole("button", { name: "Previous card" }));
208
+ react_1.screen.getByText("Q: Test question 1?");
209
+ })));
210
+ test("Flashcard shows content and can be keyboard navigated and cycles", server.boundary(() => __awaiter(void 0, void 0, void 0, function* () {
211
+ yield setup({
212
+ type: "smoot-design::tutor-drawer-open",
213
+ payload: {
214
+ blockType: "video",
215
+ target: "ai-chat",
216
+ chat: {
217
+ apiUrl: TEST_API_STREAMING,
218
+ },
219
+ summary: {
220
+ apiUrl: CONTENT_FILE_URL,
221
+ },
222
+ },
223
+ });
224
+ yield user_event_1.default.click(react_1.screen.getByRole("tab", { name: "Flashcards" }));
225
+ react_1.screen.getByText("Q: Test question 1?");
226
+ yield user_event_1.default.keyboard("{enter}");
227
+ react_1.screen.getByText("Answer: Test answer 1");
228
+ yield user_event_1.default.keyboard("{arrowright}");
229
+ react_1.screen.getByText("Q: Test question 2?");
230
+ yield user_event_1.default.keyboard("{enter}");
231
+ react_1.screen.getByText("Answer: Test answer 2");
232
+ yield user_event_1.default.keyboard("{arrowleft}");
233
+ react_1.screen.getByText("Q: Test question 1?");
234
+ yield user_event_1.default.keyboard("{arrowleft}");
235
+ react_1.screen.getByText("Q: Test question 3?");
236
+ yield user_event_1.default.keyboard("{arrowright}");
237
+ yield user_event_1.default.keyboard("{arrowright}");
238
+ yield user_event_1.default.keyboard("{arrowright}");
239
+ react_1.screen.getByText("Q: Test question 3?");
240
+ })));
241
+ });
@@ -177,4 +177,35 @@ describe("AiChat", () => {
177
177
  const alert = yield react_1.screen.findByRole("alert");
178
178
  expect(alert).toHaveTextContent("An unexpected error has occurred");
179
179
  }));
180
+ test("Shows the entry screen if entryScreenEnabled is true", () => __awaiter(void 0, void 0, void 0, function* () {
181
+ setup({
182
+ entryScreenEnabled: true,
183
+ entryScreenTitle: "Entry Screen Title",
184
+ });
185
+ yield expect(react_1.screen.getByText("Entry Screen Title")).toBeInTheDocument();
186
+ }));
187
+ test("User can submit a prompt from the entry screen", () => __awaiter(void 0, void 0, void 0, function* () {
188
+ setup({
189
+ entryScreenEnabled: true,
190
+ entryScreenTitle: "Entry Screen Title",
191
+ initialMessages: [],
192
+ conversationStarters: [],
193
+ });
194
+ yield user_event_1.default.click(react_1.screen.getByRole("textbox"));
195
+ yield user_event_1.default.paste("User message");
196
+ yield user_event_1.default.click(react_1.screen.getByRole("button", { name: "Send" }));
197
+ const messages = getMessages();
198
+ expect(messages[0]).toHaveTextContent("User message");
199
+ }));
200
+ test("User can click starter on the entry screen to submit a prompt", () => __awaiter(void 0, void 0, void 0, function* () {
201
+ setup({
202
+ entryScreenEnabled: true,
203
+ entryScreenTitle: "Entry Screen Title",
204
+ initialMessages: [],
205
+ conversationStarters: [{ content: "Starter 1" }],
206
+ });
207
+ yield user_event_1.default.click(react_1.screen.getByRole("button", { name: "Starter 1" }));
208
+ const messages = getMessages();
209
+ expect(messages[0]).toHaveTextContent("Starter 1");
210
+ }));
180
211
  });
@@ -3,7 +3,7 @@ import type { TabProps } from "@mui/material/Tab";
3
3
  import type { TabListProps } from "@mui/lab/TabList";
4
4
  import type { ButtonLinkProps } from "../Button/Button";
5
5
  type StyleVariant = "chat";
6
- export type TabButtonListProps = TabListProps & {
6
+ type TabButtonListProps = TabListProps & {
7
7
  styleVariant?: StyleVariant;
8
8
  };
9
9
  declare const TabButtonList: React.FC<TabButtonListProps>;
@@ -11,6 +11,15 @@ declare const TabLinkInner: React.ForwardRefExoticComponent<Omit<ButtonLinkProps
11
11
  type TabButtonProps = Omit<TabProps<"button">, "color"> & {
12
12
  styleVariant?: StyleVariant;
13
13
  };
14
+ /**
15
+ * Wrapper around [MUI Tab](https://mui.com/material-ui/api/tab/) using our
16
+ * Button as the `component` implementation.
17
+ */
14
18
  declare const TabButton: (props: TabButtonProps) => React.JSX.Element;
19
+ /**
20
+ * Wrapper around [MUI Tab](https://mui.com/material-ui/api/tab/) using our
21
+ * ButtonLink as the `component` implementation.
22
+ */
15
23
  declare const TabButtonLink: ({ ...props }: TabProps<typeof TabLinkInner>) => React.JSX.Element;
16
24
  export { TabButtonList, TabButton, TabButtonLink };
25
+ export type { TabButtonListProps, TabButtonProps };
@@ -23,7 +23,10 @@ const defaultTabListProps = {
23
23
  allowScrollButtonsMobile: true,
24
24
  scrollButtons: "auto",
25
25
  };
26
- const TabButtonList = (0, styled_1.default)((props) => (React.createElement(TabList_1.default, Object.assign({}, defaultTabListProps, props))))(({ theme, styleVariant }) => (Object.assign({ minHeight: "unset", ".MuiTabs-indicator": {
26
+ const TabButtonList = (0, styled_1.default)((_a) => {
27
+ var { styleVariant } = _a, props = __rest(_a, ["styleVariant"]);
28
+ return (React.createElement(TabList_1.default, Object.assign({}, defaultTabListProps, props)));
29
+ })(({ theme, styleVariant }) => (Object.assign({ minHeight: "unset", ".MuiTabs-indicator": {
27
30
  display: "none",
28
31
  }, ".MuiTabs-flexContainer": {
29
32
  gap: "8px",
@@ -77,8 +80,16 @@ const TabLinkInner = React.forwardRef((props, ref) => {
77
80
  return React.createElement(TabLinkStyled, Object.assign({}, defaultTabButtonProps, others, { ref: ref }));
78
81
  });
79
82
  TabLinkInner.displayName = "TabLinkInner";
83
+ /**
84
+ * Wrapper around [MUI Tab](https://mui.com/material-ui/api/tab/) using our
85
+ * Button as the `component` implementation.
86
+ */
80
87
  const TabButton = (props) => (React.createElement(Tab_1.default, Object.assign({}, props, { component: TabButtonInner })));
81
88
  exports.TabButton = TabButton;
89
+ /**
90
+ * Wrapper around [MUI Tab](https://mui.com/material-ui/api/tab/) using our
91
+ * ButtonLink as the `component` implementation.
92
+ */
82
93
  const TabButtonLink = (_a) => {
83
94
  var props = __rest(_a, []);
84
95
  return (React.createElement(Tab_1.default, Object.assign({}, props, { component: TabLinkInner })));
@@ -12,4 +12,5 @@ export { TextField } from "./components/TextField/TextField";
12
12
  export type { TextFieldProps } from "./components/TextField/TextField";
13
13
  export { SrAnnouncer } from "./components/SrAnnouncer/SrAnnouncer";
14
14
  export type { SrAnnouncerProps } from "./components/SrAnnouncer/SrAnnouncer";
15
+ export { TabButton, TabButtonLink, TabButtonList, } from "./components/TabButtons/TabButtonList";
15
16
  export { VisuallyHidden } from "./components/VisuallyHidden/VisuallyHidden";
package/dist/cjs/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  "use client";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.VisuallyHidden = exports.SrAnnouncer = exports.TextField = exports.Input = exports.ActionButtonLink = exports.ActionButton = exports.ButtonLink = exports.Button = exports.createTheme = exports.ThemeProvider = exports.Global = exports.css = exports.styled = void 0;
4
+ exports.VisuallyHidden = exports.TabButtonList = exports.TabButtonLink = exports.TabButton = exports.SrAnnouncer = exports.TextField = exports.Input = exports.ActionButtonLink = exports.ActionButton = exports.ButtonLink = exports.Button = exports.createTheme = exports.ThemeProvider = exports.Global = exports.css = exports.styled = void 0;
5
5
  var styled_1 = require("@emotion/styled");
6
6
  Object.defineProperty(exports, "styled", { enumerable: true, get: function () { return styled_1.default; } });
7
7
  var react_1 = require("@emotion/react");
@@ -22,5 +22,9 @@ var TextField_1 = require("./components/TextField/TextField");
22
22
  Object.defineProperty(exports, "TextField", { enumerable: true, get: function () { return TextField_1.TextField; } });
23
23
  var SrAnnouncer_1 = require("./components/SrAnnouncer/SrAnnouncer");
24
24
  Object.defineProperty(exports, "SrAnnouncer", { enumerable: true, get: function () { return SrAnnouncer_1.SrAnnouncer; } });
25
+ var TabButtonList_1 = require("./components/TabButtons/TabButtonList");
26
+ Object.defineProperty(exports, "TabButton", { enumerable: true, get: function () { return TabButtonList_1.TabButton; } });
27
+ Object.defineProperty(exports, "TabButtonLink", { enumerable: true, get: function () { return TabButtonList_1.TabButtonLink; } });
28
+ Object.defineProperty(exports, "TabButtonList", { enumerable: true, get: function () { return TabButtonList_1.TabButtonList; } });
25
29
  var VisuallyHidden_1 = require("./components/VisuallyHidden/VisuallyHidden");
26
30
  Object.defineProperty(exports, "VisuallyHidden", { enumerable: true, get: function () { return VisuallyHidden_1.VisuallyHidden; } });
@@ -72,12 +72,12 @@ export const FlashcardsScreen = ({ flashcards, wasKeyboardFocus, }) => {
72
72
  return (React.createElement(Container, { ref: containerRef },
73
73
  React.createElement(Flashcard, { ref: flashcardRef, content: flashcards[cardIndex], "aria-label": `Flashcard ${cardIndex + 1} of ${flashcards.length}` }),
74
74
  React.createElement(Navigation, null,
75
- React.createElement(ActionButton, { onClick: () => setCardIndex(cardIndex - 1), disabled: cardIndex === 0, variant: "secondary", color: "secondary", size: "small" },
75
+ React.createElement(ActionButton, { onClick: () => setCardIndex(cardIndex - 1), disabled: cardIndex === 0, variant: "secondary", color: "secondary", size: "small", "aria-label": "Previous card" },
76
76
  React.createElement(RiArrowLeftLine, { "aria-hidden": true })),
77
77
  React.createElement(Page, null,
78
78
  cardIndex + 1,
79
79
  " / ",
80
80
  flashcards.length),
81
- React.createElement(ActionButton, { onClick: () => setCardIndex(cardIndex + 1), disabled: cardIndex === flashcards.length - 1, variant: "secondary", color: "secondary", size: "small" },
81
+ React.createElement(ActionButton, { onClick: () => setCardIndex(cardIndex + 1), disabled: cardIndex === flashcards.length - 1, variant: "secondary", color: "secondary", size: "small", "aria-label": "Next card" },
82
82
  React.createElement(RiArrowRightLine, { "aria-hidden": true })))));
83
83
  };
@@ -7,6 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ // @format
10
11
  import * as React from "react";
11
12
  import { useEffect, useState, useRef, useMemo } from "react";
12
13
  import styled from "@emotion/styled";
@@ -212,26 +213,28 @@ const RemoteTutorDrawer = ({ messageOrigin, transformBody = identity, className,
212
213
  };
213
214
  }, [messageOrigin, target]);
214
215
  useEffect(() => {
215
- scrollElement === null || scrollElement === void 0 ? void 0 : scrollElement.scrollTo({
216
+ var _a;
217
+ (_a = scrollElement === null || scrollElement === void 0 ? void 0 : scrollElement.scrollTo) === null || _a === void 0 ? void 0 : _a.call(scrollElement, {
216
218
  top: tab === "chat" ? scrollElement.scrollHeight : 0,
217
219
  });
218
220
  }, [tab, scrollElement]);
219
221
  const conversationStarters = useMemo(() => {
222
+ var _a;
220
223
  if (!payload)
221
224
  return [];
222
225
  return (payload.chat.conversationStarters ||
223
- ((response === null || response === void 0 ? void 0 : response.flashcards)
226
+ (((_a = response === null || response === void 0 ? void 0 : response.flashcards) === null || _a === void 0 ? void 0 : _a.length) && response.flashcards.length >= 3
224
227
  ? randomItems(response.flashcards, 3).map((flashcard) => ({
225
228
  content: flashcard.question,
226
229
  }))
227
230
  : DEFAULT_VIDEO_STARTERS));
228
231
  }, [payload, response]);
229
232
  if (!payload) {
230
- return null;
233
+ return React.createElement("div", { "data-testid": "remote-tutor-drawer-waiting" });
231
234
  }
232
235
  const { blockType, chat } = payload;
233
236
  const hasTabs = blockType === "video";
234
- return (React.createElement(Drawer, { className: className, PaperProps: {
237
+ return (React.createElement(Drawer, { "data-testid": "remote-tutor-drawer", className: className, PaperProps: {
235
238
  ref: paperRefCallback,
236
239
  sx: {
237
240
  width: "900px",
@@ -246,7 +249,7 @@ const RemoteTutorDrawer = ({ messageOrigin, transformBody = identity, className,
246
249
  React.createElement(Header, null,
247
250
  React.createElement(Title, null,
248
251
  payload.title ? React.createElement(RiSparkling2Line, null) : null,
249
- React.createElement(Typography, { variant: "body1" }, ((_b = payload.title) === null || _b === void 0 ? void 0 : _b.includes("AskTIM")) ? (React.createElement(React.Fragment, null,
252
+ React.createElement(Typography, { variant: "body1", component: "h2" }, ((_b = payload.title) === null || _b === void 0 ? void 0 : _b.includes("AskTIM")) ? (React.createElement(React.Fragment, null,
250
253
  "Ask",
251
254
  React.createElement("strong", null, "TIM"),
252
255
  payload.title.replace("AskTIM", ""))) : (payload.title))),