@mitodl/smoot-design 3.4.0 → 3.5.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.
Files changed (30) hide show
  1. package/dist/bundles/remoteAiChatDrawer.es.js +13741 -12492
  2. package/dist/bundles/remoteAiChatDrawer.umd.js +114 -41
  3. package/dist/cjs/bundles/RemoteAiChatDrawer/RemoteAiChatDrawer.stories.js +6 -0
  4. package/dist/cjs/components/AiChat/AiChat.js +36 -33
  5. package/dist/cjs/components/AiChat/AiChat.stories.d.ts +4 -0
  6. package/dist/cjs/components/AiChat/AiChat.stories.js +40 -14
  7. package/dist/cjs/components/AiChat/AiChat.test.js +25 -17
  8. package/dist/cjs/components/AiChat/test-utils/api.d.ts +2 -0
  9. package/dist/{esm/components/AiChat/story-utils.js → cjs/components/AiChat/test-utils/api.js} +77 -51
  10. package/dist/cjs/components/Alert/Alert.d.ts +15 -0
  11. package/dist/cjs/components/Alert/Alert.js +62 -0
  12. package/dist/cjs/components/Alert/Alert.stories.d.ts +8 -0
  13. package/dist/cjs/components/Alert/Alert.stories.js +53 -0
  14. package/dist/cjs/components/Input/Input.js +8 -6
  15. package/dist/esm/bundles/RemoteAiChatDrawer/RemoteAiChatDrawer.stories.js +6 -0
  16. package/dist/esm/components/AiChat/AiChat.js +36 -33
  17. package/dist/esm/components/AiChat/AiChat.stories.d.ts +4 -0
  18. package/dist/esm/components/AiChat/AiChat.stories.js +39 -13
  19. package/dist/esm/components/AiChat/AiChat.test.js +25 -17
  20. package/dist/esm/components/AiChat/test-utils/api.d.ts +2 -0
  21. package/dist/{cjs/components/AiChat/story-utils.js → esm/components/AiChat/test-utils/api.js} +74 -55
  22. package/dist/esm/components/Alert/Alert.d.ts +15 -0
  23. package/dist/esm/components/Alert/Alert.js +59 -0
  24. package/dist/esm/components/Alert/Alert.stories.d.ts +8 -0
  25. package/dist/esm/components/Alert/Alert.stories.js +50 -0
  26. package/dist/esm/components/Input/Input.js +8 -6
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +9 -1
  29. package/dist/cjs/components/AiChat/story-utils.d.ts +0 -3
  30. package/dist/esm/components/AiChat/story-utils.d.ts +0 -3
@@ -5,6 +5,7 @@ exports.StreamingResponses = void 0;
5
5
  const React = require("react");
6
6
  const RemoteAiChatDrawer_1 = require("./RemoteAiChatDrawer");
7
7
  const tiny_invariant_1 = require("tiny-invariant");
8
+ const api_1 = require("../../components/AiChat/test-utils/api");
8
9
  const TEST_API_STREAMING = "http://localhost:4567/streaming";
9
10
  const INITIAL_MESSAGES = [
10
11
  {
@@ -58,6 +59,11 @@ const meta = {
58
59
  }, title: "button frame" }),
59
60
  React.createElement(RemoteAiChatDrawer_1.AiChatDrawer, { messageOrigin: "http://localhost:6006" })));
60
61
  },
62
+ parameters: {
63
+ msw: {
64
+ handlers: api_1.handlers,
65
+ },
66
+ },
61
67
  };
62
68
  exports.default = meta;
63
69
  exports.StreamingResponses = {};
@@ -24,6 +24,7 @@ const classnames_1 = require("classnames");
24
24
  const SrAnnouncer_1 = require("../SrAnnouncer/SrAnnouncer");
25
25
  const VisuallyHidden_1 = require("../VisuallyHidden/VisuallyHidden");
26
26
  const Typography_1 = require("@mui/material/Typography");
27
+ const Alert_1 = require("../Alert/Alert");
27
28
  const classes = {
28
29
  root: "MitAiChat--root",
29
30
  title: "MitAiChat--title",
@@ -34,6 +35,7 @@ const classes = {
34
35
  messageRowAssistant: "MitAiChat--messageRowAssistant",
35
36
  message: "MitAiChat--message",
36
37
  input: "MitAiChat--input",
38
+ bottomSection: "MitAiChat--bottomSection",
37
39
  };
38
40
  const ChatContainer = styled_1.default.div({
39
41
  width: "100%",
@@ -66,10 +68,9 @@ const MessagesContainer = (0, styled_1.default)(ScrollSnap_1.ScrollSnap)({
66
68
  display: "flex",
67
69
  flexDirection: "column",
68
70
  flex: 1,
69
- paddingTop: "14px",
70
- paddingBottom: "24px",
71
+ padding: "14px 0",
71
72
  overflow: "auto",
72
- gap: "24px",
73
+ gap: "16px",
73
74
  });
74
75
  const MessageRow = styled_1.default.div({
75
76
  display: "flex",
@@ -87,16 +88,18 @@ const Message = styled_1.default.div(({ theme }) => (Object.assign(Object.assign
87
88
  marginTop: 0,
88
89
  }, "p:last-of-type": {
89
90
  marginBottom: 0,
91
+ }, "ol, ul": {
92
+ paddingInlineStart: "32px",
93
+ li: {
94
+ margin: "16px 0",
95
+ },
96
+ }, ul: {
97
+ marginInlineStart: "-16px",
90
98
  }, a: {
91
99
  color: theme.custom.colors.red,
92
100
  fontWeight: "normal",
93
101
  }, borderRadius: "12px", [`.${classes.messageRowAssistant} &`]: {
94
- border: `1px solid ${theme.custom.colors.lightGray2}`,
95
- borderRadius: "0px 8px 8px 8px",
96
- svg: {
97
- fill: theme.custom.colors.silverGrayDark,
98
- display: "block",
99
- },
102
+ padding: "12px 16px 12px 0",
100
103
  }, [`.${classes.messageRowUser} &`]: {
101
104
  borderRadius: "8px 0px 8px 8px",
102
105
  backgroundColor: theme.custom.colors.lightGray1,
@@ -117,20 +120,18 @@ const RobotIcon = (0, styled_1.default)(react_1.RiRobot2Line)({
117
120
  width: "40px",
118
121
  height: "40px",
119
122
  });
120
- const StyledInput = (0, styled_1.default)(Input_1.Input)(({ theme }) => ({
121
- backgroundColor: theme.custom.colors.lightGray1,
122
- borderRadius: "8px",
123
- border: `1px solid ${theme.custom.colors.lightGray2}`,
124
- }));
125
123
  const StyledSendButton = (0, styled_1.default)(react_1.RiSendPlaneFill)(({ theme }) => ({
126
124
  fill: theme.custom.colors.red,
127
125
  }));
128
126
  const StyledStopButton = (0, styled_1.default)(react_1.RiStopFill)(({ theme }) => ({
129
127
  fill: theme.custom.colors.red,
130
128
  }));
129
+ const BottomSection = styled_1.default.div({
130
+ paddingTop: "12px",
131
+ });
131
132
  const Disclaimer = (0, styled_1.default)(Typography_1.default)(({ theme }) => ({
132
133
  color: theme.custom.colors.silverGrayDark,
133
- marginTop: "16px",
134
+ marginTop: "14px",
134
135
  textAlign: "center",
135
136
  }));
136
137
  const ChatTitle = (0, styled_1.default)(({ title, askTimTitle, onClose, className }) => {
@@ -165,7 +166,7 @@ const AiChatInternal = function AiChat(_a) {
165
166
  const prefix = Math.random().toString().slice(2);
166
167
  return initMsgs.map((m, i) => (Object.assign(Object.assign({}, m), { id: `initial-${prefix}-${i}` })));
167
168
  }, [initMsgs]);
168
- const { messages: unparsed, input, handleInputChange, handleSubmit, append, isLoading, stop, } = (0, utils_1.useAiChat)(requestOpts, {
169
+ const { messages: unparsed, input, handleInputChange, handleSubmit, append, isLoading, stop, error, } = (0, utils_1.useAiChat)(requestOpts, {
169
170
  initialMessages: initialMessages,
170
171
  id: chatId,
171
172
  });
@@ -181,7 +182,7 @@ const AiChatInternal = function AiChat(_a) {
181
182
  });
182
183
  }, [parseContent, unparsed, initialMessages]);
183
184
  const showStarters = messages.length === initialMessages.length;
184
- const waiting = !showStarters && ((_b = messages[messages.length - 1]) === null || _b === void 0 ? void 0 : _b.role) === "user";
185
+ const waiting = !showStarters && !error && ((_b = messages[messages.length - 1]) === null || _b === void 0 ? void 0 : _b.role) === "user";
185
186
  const stoppable = isLoading && ((_c = messages[messages.length - 1]) === null || _c === void 0 ? void 0 : _c.role) !== "user";
186
187
  const scrollToBottom = () => {
187
188
  var _a;
@@ -207,24 +208,26 @@ const AiChatInternal = function AiChat(_a) {
207
208
  } }, m.content))))) : null,
208
209
  waiting ? (React.createElement(MessageRow, { className: (0, classnames_1.default)(classes.messageRow, classes.messageRowAssistant), key: "loading" },
209
210
  React.createElement(Message, null,
210
- React.createElement(react_1.RiMoreFill, null)))) : null),
211
- React.createElement("form", { onSubmit: (e) => {
212
- e.preventDefault();
213
- if (isLoading && stoppable) {
214
- stop();
215
- }
216
- else {
217
- scrollToBottom();
218
- handleSubmit(e);
219
- }
220
- } },
221
- React.createElement(StyledInput, { fullWidth: true, size: "chat", className: classes.input, placeholder: placeholder, name: "message", sx: { flex: 1 }, value: input, onChange: handleInputChange, endAdornment: isLoading ? (React.createElement(Input_1.AdornmentButton, { "aria-label": "Stop", onClick: stop, disabled: !stoppable },
222
- React.createElement(StyledStopButton, null))) : (React.createElement(Input_1.AdornmentButton, { "aria-label": "Send", type: "submit", disabled: !input, onClick: (e) => {
211
+ React.createElement(react_1.RiMoreFill, null)))) : null,
212
+ error ? (React.createElement(Alert_1.Alert, { severity: "error", closable: true }, "An unexpected error has occurred.")) : null),
213
+ React.createElement(BottomSection, { className: classes.bottomSection },
214
+ React.createElement("form", { onSubmit: (e) => {
215
+ e.preventDefault();
216
+ if (isLoading && stoppable) {
217
+ stop();
218
+ }
219
+ else {
223
220
  scrollToBottom();
224
221
  handleSubmit(e);
225
- } },
226
- React.createElement(StyledSendButton, null))) })),
227
- React.createElement(Disclaimer, { variant: "body3" }, "AI-generated content may be incorrect."),
222
+ }
223
+ } },
224
+ React.createElement(Input_1.Input, { fullWidth: true, size: "chat", className: classes.input, placeholder: placeholder, name: "message", sx: { flex: 1 }, value: input, onChange: handleInputChange, endAdornment: isLoading ? (React.createElement(Input_1.AdornmentButton, { "aria-label": "Stop", onClick: stop, disabled: !stoppable },
225
+ React.createElement(StyledStopButton, null))) : (React.createElement(Input_1.AdornmentButton, { "aria-label": "Send", type: "submit", onClick: (e) => {
226
+ scrollToBottom();
227
+ handleSubmit(e);
228
+ } },
229
+ React.createElement(StyledSendButton, null))) })),
230
+ React.createElement(Disclaimer, { variant: "body3" }, "AI-generated content may be incorrect.")),
228
231
  React.createElement(SrAnnouncer_1.SrAnnouncer, { isLoading: isLoading, loadingMessages: srLoadingMessages, message: lastMsg.role === "assistant" ? lastMsg.content : "" })));
229
232
  };
230
233
  const AiChat = (props) => (
@@ -9,3 +9,7 @@ export declare const StreamingResponses: Story;
9
9
  * to text via `parseContent`.
10
10
  */
11
11
  export declare const JsonResponses: Story;
12
+ /**
13
+ * This story shows the component's builtin markdown styling.
14
+ */
15
+ export declare const MarkdownStyling: Story;
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.JsonResponses = exports.StreamingResponses = void 0;
3
+ exports.MarkdownStyling = exports.JsonResponses = exports.StreamingResponses = void 0;
4
4
  const React = require("react");
5
5
  const AiChat_1 = require("./AiChat");
6
- const story_utils_1 = require("./story-utils");
7
6
  const styled_1 = require("@emotion/styled");
8
7
  const test_1 = require("@storybook/test");
8
+ const api_1 = require("./test-utils/api");
9
9
  const TEST_API_STREAMING = "http://localhost:4567/streaming";
10
10
  const TEST_API_JSON = "http://localhost:4567/json";
11
11
  const INITIAL_MESSAGES = [
@@ -34,6 +34,9 @@ const Container = styled_1.default.div({
34
34
  const meta = {
35
35
  title: "smoot-design/AI/AiChat",
36
36
  component: AiChat_1.AiChat,
37
+ parameters: {
38
+ msw: { handlers: api_1.handlers },
39
+ },
37
40
  render: (args) => React.createElement(AiChat_1.AiChat, Object.assign({}, args)),
38
41
  decorators: (Story) => {
39
42
  return (React.createElement(Container, null,
@@ -58,18 +61,6 @@ const meta = {
58
61
  table: { readonly: true }, // See above
59
62
  },
60
63
  },
61
- beforeEach: () => {
62
- const originalFetch = window.fetch;
63
- window.fetch = (url, opts) => {
64
- if (url === TEST_API_STREAMING) {
65
- return (0, story_utils_1.mockStreaming)();
66
- }
67
- else if (url === TEST_API_JSON) {
68
- return (0, story_utils_1.mockJson)();
69
- }
70
- return originalFetch(url, opts);
71
- };
72
- },
73
64
  };
74
65
  exports.default = meta;
75
66
  exports.StreamingResponses = {};
@@ -86,3 +77,38 @@ exports.JsonResponses = {
86
77
  },
87
78
  },
88
79
  };
80
+ const DEMO_MARKDOWN = `This shows default markdown styling. Here's a list:
81
+ - Item 1 lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
82
+ - Item 2
83
+ - Item 3
84
+ - Item 3.1
85
+ - Item 3.2
86
+ - Item 4
87
+ 1. Item 4.1
88
+ 2. Item 4.2
89
+ 3. Item 4.3
90
+
91
+ Here is a longer paragraph and **bold text** and *italic text*. Lorem ipsum dolor sit amet, consectetur adipiscing elit
92
+ sed do eiusmod tempor [incididunt](https://mit.edu) ut labore et dolore magna aliqua. Ut enim ad minim veniam.
93
+
94
+ And some inline code, \`\`<inline></inline>\`\` and code block:
95
+ \`\`\`
96
+ def f(x):
97
+ print(x)
98
+ \`\`\`
99
+ `;
100
+ /**
101
+ * This story shows the component's builtin markdown styling.
102
+ */
103
+ exports.MarkdownStyling = {
104
+ args: {
105
+ title: "Markdown Styles",
106
+ requestOpts: { apiUrl: TEST_API_STREAMING },
107
+ initialMessages: [
108
+ {
109
+ role: "assistant",
110
+ content: DEMO_MARKDOWN,
111
+ },
112
+ ],
113
+ },
114
+ };
@@ -17,17 +17,18 @@ const AiChat_1 = require("./AiChat");
17
17
  const ThemeProvider_1 = require("../ThemeProvider/ThemeProvider");
18
18
  const React = require("react");
19
19
  const en_1 = require("@faker-js/faker/locale/en");
20
+ const msw_1 = require("msw");
21
+ const node_1 = require("msw/node");
20
22
  const counter = jest.fn(); // use jest.fn as counter because it resets on each test
21
- const mockFetch = jest.mocked(jest.fn(() => {
23
+ const API_URL = "http://localhost:4567/test";
24
+ const server = (0, node_1.setupServer)(msw_1.http.post(API_URL, () => __awaiter(void 0, void 0, void 0, function* () {
22
25
  const count = counter.mock.calls.length;
23
26
  counter();
24
- return Promise.resolve(new Response(`AI Response ${count}`, {
25
- headers: {
26
- "Content-Type": "application/json",
27
- },
28
- }));
29
- }));
30
- window.fetch = mockFetch;
27
+ return msw_1.HttpResponse.text(`AI Response ${count}`);
28
+ })));
29
+ beforeAll(() => server.listen());
30
+ afterEach(() => server.resetHandlers());
31
+ afterAll(() => server.close());
31
32
  jest.mock("react-markdown", () => {
32
33
  return {
33
34
  __esModule: true,
@@ -60,9 +61,9 @@ describe("AiChat", () => {
60
61
  { content: en_1.faker.lorem.sentence() },
61
62
  { content: en_1.faker.lorem.sentence() },
62
63
  ];
63
- const view = (0, react_1.render)(React.createElement(AiChat_1.AiChat, Object.assign({ "data-testid": "ai-chat", initialMessages: initialMessages, conversationStarters: conversationStarters, requestOpts: { apiUrl: "http://localhost:4567/test" }, placeholder: "Type a message..." }, props)), { wrapper: ThemeProvider_1.ThemeProvider });
64
+ const view = (0, react_1.render)(React.createElement(AiChat_1.AiChat, Object.assign({ "data-testid": "ai-chat", initialMessages: initialMessages, conversationStarters: conversationStarters, requestOpts: { apiUrl: API_URL }, placeholder: "Type a message..." }, props)), { wrapper: ThemeProvider_1.ThemeProvider });
64
65
  const rerender = (newProps) => {
65
- view.rerender(React.createElement(AiChat_1.AiChat, Object.assign({ "data-testid": "ai-chat", initialMessages: initialMessages, conversationStarters: conversationStarters, requestOpts: { apiUrl: "http://localhost:4567/test" } }, newProps)));
66
+ view.rerender(React.createElement(AiChat_1.AiChat, Object.assign({ "data-testid": "ai-chat", initialMessages: initialMessages, conversationStarters: conversationStarters, requestOpts: { apiUrl: API_URL } }, newProps)));
66
67
  };
67
68
  return { initialMessages, conversationStarters, rerender };
68
69
  };
@@ -108,11 +109,11 @@ describe("AiChat", () => {
108
109
  yield whenCount(getMessages, 3);
109
110
  }));
110
111
  test("transformBody is called before sending requests", () => __awaiter(void 0, void 0, void 0, function* () {
112
+ const mockFetch = jest.spyOn(window, "fetch");
111
113
  const fakeBody = { message: en_1.faker.lorem.sentence() };
112
- const apiUrl = en_1.faker.internet.url();
113
114
  const transformBody = jest.fn(() => fakeBody);
114
115
  const { initialMessages } = setup({
115
- requestOpts: { apiUrl, transformBody },
116
+ requestOpts: { apiUrl: API_URL, transformBody },
116
117
  });
117
118
  yield user_event_1.default.click(react_1.screen.getByPlaceholderText("Type a message..."));
118
119
  yield user_event_1.default.paste("User message");
@@ -122,16 +123,15 @@ describe("AiChat", () => {
122
123
  expect.objectContaining({ content: "User message", role: "user" }),
123
124
  ]);
124
125
  expect(mockFetch).toHaveBeenCalledTimes(1);
125
- expect(mockFetch).toHaveBeenCalledWith(apiUrl, expect.objectContaining({
126
+ expect(mockFetch).toHaveBeenCalledWith(API_URL, expect.objectContaining({
126
127
  body: JSON.stringify(fakeBody),
127
128
  }));
128
129
  }));
129
130
  test("parseContent is called on the API-received message content", () => __awaiter(void 0, void 0, void 0, function* () {
130
131
  const fakeBody = { message: en_1.faker.lorem.sentence() };
131
- const apiUrl = en_1.faker.internet.url();
132
132
  const transformBody = jest.fn(() => fakeBody);
133
133
  const { initialMessages, conversationStarters } = setup({
134
- requestOpts: { apiUrl, transformBody },
134
+ requestOpts: { apiUrl: API_URL, transformBody },
135
135
  parseContent: jest.fn((content) => `Parsed: ${content}`),
136
136
  });
137
137
  yield user_event_1.default.click(getConversationStarters()[0]);
@@ -151,12 +151,20 @@ describe("AiChat", () => {
151
151
  }));
152
152
  test("Passes extra attributes to root", () => {
153
153
  const fakeBody = { message: en_1.faker.lorem.sentence() };
154
- const apiUrl = en_1.faker.internet.url();
155
154
  const transformBody = jest.fn(() => fakeBody);
156
155
  setup({
157
- requestOpts: { apiUrl, transformBody },
156
+ requestOpts: { apiUrl: API_URL, transformBody },
158
157
  parseContent: jest.fn((content) => `Parsed: ${content}`),
159
158
  });
160
159
  expect(react_1.screen.getByTestId("ai-chat")).toBeInTheDocument();
161
160
  });
161
+ test("If the API returns an error, an alert is shown", () => __awaiter(void 0, void 0, void 0, function* () {
162
+ setup();
163
+ server.use(msw_1.http.post(API_URL, () => __awaiter(void 0, void 0, void 0, function* () {
164
+ return new msw_1.HttpResponse(null, { status: 500 });
165
+ })));
166
+ yield user_event_1.default.click(getConversationStarters()[0]);
167
+ const alert = yield react_1.screen.findByRole("alert");
168
+ expect(alert).toHaveTextContent("An unexpected error has occurred");
169
+ }));
162
170
  });
@@ -0,0 +1,2 @@
1
+ declare const handlers: import("msw").HttpHandler[];
2
+ export { handlers };
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -7,6 +8,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
9
  });
9
10
  };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.handlers = void 0;
13
+ const msw_1 = require("msw");
10
14
  const SAMPLE_RESPONSES = [
11
15
  `For exploring AI applications in business, I recommend the following course from MIT:
12
16
 
@@ -37,63 +41,85 @@ Here are some courses on linear algebra that you can explore:
37
41
  These courses provide a comprehensive introduction to linear algebra and its applications across various fields.
38
42
  <!-- Comment! -->
39
43
  `,
44
+ `Here are some courses on quantum computing that offer certificates:
45
+
46
+ 1. [Introduction to Quantum Computing](https://xpro.mit.edu/courses/course-v1:xPRO+QCFx1/)
47
+ - **Description**: This is the first course in the Quantum Computing Fundamentals professional certificate program. You can earn a Professional Certificate and CEUs by completing both courses in the program. Alternatively, you can take this course individually for a certificate of completion and CEUs.
48
+ - **Offered by**: MIT xPRO
49
+ - **Instructors**: Isaac Chuang, William Oliver, Peter Shor, Aram Harrow
50
+
51
+ 2. [Practical Realities of Quantum Computation and Quantum Communication](https://xpro.mit.edu/courses/course-v1:xPRO+QCRx1/)
52
+ - **Description**: This course is part of the Quantum Computing Realities professional certificate program. Completing both courses in the program will earn you a Professional Certificate and CEUs. You can also take this course individually for a certificate of completion and CEUs.
53
+ - **Offered by**: MIT xPRO
54
+ - **Instructors**: Isaac Chuang, William Oliver, Peter Shor, Aram Harrow
55
+
56
+ These courses are part of professional certificate programs, and you can choose to complete the entire program or take individual courses for certification.`,
40
57
  ];
41
58
  const rand = (min, max) => {
42
59
  // min and max included
43
60
  return Math.floor(Math.random() * (max - min + 1) + min);
44
61
  };
45
- const mockStreaming = function mockApi() {
46
- return __awaiter(this, void 0, void 0, function* () {
47
- let timerId;
48
- const response = SAMPLE_RESPONSES[rand(0, SAMPLE_RESPONSES.length - 1)];
49
- const chunks = response.split(" ").reduce((acc, word) => {
50
- const last = acc[acc.length - 1];
51
- if (acc.length === 0) {
52
- acc.push(word);
53
- }
54
- else if (Math.random() < 0.75) {
55
- acc[acc.length - 1] = `${last} ${word}`;
56
- }
57
- else {
58
- acc.push(` ${word}`);
59
- }
60
- return acc;
61
- }, []);
62
- const num = chunks.length;
63
- let i = 0;
64
- yield new Promise((resolve) => setTimeout(resolve, 800));
65
- const body = new ReadableStream({
66
- start(controller) {
67
- timerId = setInterval(() => {
68
- const msg = new TextEncoder().encode(chunks[i]);
69
- controller.enqueue(msg);
70
- i++;
71
- if (i === num) {
72
- controller.close();
73
- clearInterval(timerId);
74
- }
75
- }, 100);
76
- },
77
- cancel() {
78
- if (timerId) {
62
+ const getReadableStream = () => {
63
+ let timerId;
64
+ const response = SAMPLE_RESPONSES[rand(0, SAMPLE_RESPONSES.length - 1)];
65
+ const chunks = response.split(" ").reduce((acc, word) => {
66
+ const last = acc[acc.length - 1];
67
+ if (acc.length === 0) {
68
+ acc.push(word);
69
+ }
70
+ else if (Math.random() < 0.75) {
71
+ acc[acc.length - 1] = `${last} ${word}`;
72
+ }
73
+ else {
74
+ acc.push(` ${word}`);
75
+ }
76
+ return acc;
77
+ }, []);
78
+ const num = chunks.length;
79
+ let i = 0;
80
+ return new ReadableStream({
81
+ start(controller) {
82
+ timerId = setInterval(() => {
83
+ const msg = new TextEncoder().encode(chunks[i]);
84
+ controller.enqueue(msg);
85
+ i++;
86
+ if (i === num) {
87
+ controller.close();
79
88
  clearInterval(timerId);
80
89
  }
81
- },
82
- });
83
- return Promise.resolve(new Response(body, {
84
- headers: {
85
- "Content-Type": "text/event-stream",
86
- },
87
- }));
90
+ }, 100);
91
+ },
92
+ cancel() {
93
+ if (timerId) {
94
+ clearInterval(timerId);
95
+ }
96
+ },
88
97
  });
89
98
  };
90
- const mockJson = () => __awaiter(void 0, void 0, void 0, function* () {
91
- const message = SAMPLE_RESPONSES[rand(0, SAMPLE_RESPONSES.length - 1)];
92
- yield new Promise((res) => setTimeout(res, 1000));
93
- return Promise.resolve(new Response(JSON.stringify({ message }), {
94
- headers: {
95
- "Content-Type": "application/json",
96
- },
97
- }));
98
- });
99
- export { mockStreaming, mockJson };
99
+ const handlers = [
100
+ msw_1.http.post("http://localhost:4567/streaming", (_a) => __awaiter(void 0, [_a], void 0, function* ({ request }) {
101
+ yield (0, msw_1.delay)(600);
102
+ const body = getReadableStream();
103
+ const requestBody = yield request.json();
104
+ if (Array.isArray(requestBody)) {
105
+ const last = requestBody[requestBody.length - 1];
106
+ const { content } = last;
107
+ if (content === "error") {
108
+ return new msw_1.HttpResponse("Internal Server Error", {
109
+ status: 500,
110
+ });
111
+ }
112
+ }
113
+ return new msw_1.HttpResponse(body, {
114
+ headers: {
115
+ "Content-Type": "text/plain",
116
+ },
117
+ });
118
+ })),
119
+ msw_1.http.post("http://localhost:4567/json", () => __awaiter(void 0, void 0, void 0, function* () {
120
+ const message = SAMPLE_RESPONSES[rand(0, SAMPLE_RESPONSES.length - 1)];
121
+ yield (0, msw_1.delay)(800);
122
+ return msw_1.HttpResponse.json({ message });
123
+ })),
124
+ ];
125
+ exports.handlers = handlers;
@@ -0,0 +1,15 @@
1
+ import * as React from "react";
2
+ import type { AlertColor } from "@mui/material/Alert";
3
+ type AlertProps = {
4
+ visible?: boolean;
5
+ closable?: boolean;
6
+ className?: string;
7
+ severity?: AlertColor;
8
+ /**
9
+ * Alert Content
10
+ */
11
+ children?: React.ReactNode;
12
+ };
13
+ declare const Alert: React.FC<AlertProps>;
14
+ export { Alert };
15
+ export type { AlertProps };
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Alert = void 0;
5
+ const React = require("react");
6
+ const styled_1 = require("@emotion/styled");
7
+ const Alert_1 = require("@mui/material/Alert");
8
+ const getColor = (theme, severity) => {
9
+ return {
10
+ info: theme.custom.colors.blue,
11
+ success: theme.custom.colors.green,
12
+ warning: theme.custom.colors.orange,
13
+ error: theme.custom.colors.lightRed,
14
+ }[severity];
15
+ };
16
+ const AlertStyled = (0, styled_1.default)(Alert_1.default)(({ theme, severity }) => ({
17
+ padding: "11px 16px",
18
+ borderRadius: 4,
19
+ borderWidth: 2,
20
+ borderStyle: "solid",
21
+ borderColor: getColor(theme, severity),
22
+ background: "#FFF",
23
+ ".MuiAlert-message": Object.assign(Object.assign({}, theme.typography.body2), { color: theme.custom.colors.darkGray2, alignSelf: "center" }),
24
+ "> div": {
25
+ paddingTop: 0,
26
+ paddingBottom: 0,
27
+ },
28
+ ".MuiAlert-icon": {
29
+ marginRight: 8,
30
+ svg: {
31
+ width: 16,
32
+ fill: getColor(theme, severity),
33
+ },
34
+ },
35
+ button: {
36
+ padding: 0,
37
+ ":hover": {
38
+ margin: 0,
39
+ background: "none",
40
+ },
41
+ },
42
+ }));
43
+ const Hidden = styled_1.default.span({ display: "none" });
44
+ const Alert = ({ visible = true, severity = "info", closable, children, className, }) => {
45
+ const [_visible, setVisible] = React.useState(visible);
46
+ const id = React.useId();
47
+ const onCloseClick = () => {
48
+ setVisible(false);
49
+ };
50
+ React.useEffect(() => {
51
+ setVisible(visible);
52
+ }, [visible]);
53
+ if (!_visible) {
54
+ return null;
55
+ }
56
+ return (React.createElement(AlertStyled, { severity: severity, onClose: closable ? onCloseClick : undefined, role: "alert", "aria-describedby": id, className: className },
57
+ children,
58
+ React.createElement(Hidden, { id: id },
59
+ severity,
60
+ " message")));
61
+ };
62
+ exports.Alert = Alert;
@@ -0,0 +1,8 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Alert } from "./Alert";
3
+ declare const meta: Meta<typeof Alert>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof Alert>;
6
+ export declare const Basic: Story;
7
+ export declare const Closable: Story;
8
+ export declare const Variants: Story;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Variants = exports.Closable = exports.Basic = void 0;
4
+ const React = require("react");
5
+ const Alert_1 = require("./Alert");
6
+ const Stack_1 = require("@mui/material/Stack");
7
+ const meta = {
8
+ title: "smoot-design/Alert",
9
+ component: Alert_1.Alert,
10
+ };
11
+ exports.default = meta;
12
+ exports.Basic = {
13
+ args: {
14
+ severity: "info",
15
+ },
16
+ render: (args) => (React.createElement(Alert_1.Alert, Object.assign({}, args),
17
+ "Alert with severity \"",
18
+ args.severity,
19
+ "\"")),
20
+ };
21
+ exports.Closable = {
22
+ args: {
23
+ severity: "warning",
24
+ closable: true,
25
+ },
26
+ render: (args) => (React.createElement(Alert_1.Alert, Object.assign({}, args),
27
+ "Closable alert with severity \"",
28
+ args.severity,
29
+ "\"")),
30
+ };
31
+ exports.Variants = {
32
+ argTypes: {
33
+ severity: {
34
+ table: {
35
+ disable: true,
36
+ },
37
+ },
38
+ closable: {
39
+ table: {
40
+ disable: true,
41
+ },
42
+ },
43
+ },
44
+ render: (args) => (React.createElement(Stack_1.default, { direction: "column", gap: 2, sx: { my: 2 } },
45
+ React.createElement(Alert_1.Alert, Object.assign({}, args, { severity: "info" }), "Alert with severity \"info\""),
46
+ React.createElement(Alert_1.Alert, Object.assign({}, args, { closable: true, severity: "info" }), "Closable alert with severity \"info\""),
47
+ React.createElement(Alert_1.Alert, Object.assign({}, args, { severity: "success" }), "Alert with severity \"success\""),
48
+ React.createElement(Alert_1.Alert, Object.assign({}, args, { closable: true, severity: "success" }), "Closable alert with severity \"success\""),
49
+ React.createElement(Alert_1.Alert, Object.assign({}, args, { severity: "warning" }), "Alert with severity \"warning\""),
50
+ React.createElement(Alert_1.Alert, Object.assign({}, args, { closable: true, severity: "warning" }), "Closable alert with severity \"warning\""),
51
+ React.createElement(Alert_1.Alert, Object.assign({}, args, { severity: "error" }), "Alert with severity \"error\""),
52
+ React.createElement(Alert_1.Alert, Object.assign({}, args, { closable: true, severity: "error" }), "Closable alert with severity \"error\""))),
53
+ };