@mitodl/smoot-design 3.3.0 → 3.4.1

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 (42) hide show
  1. package/dist/bundles/remoteAiChatDrawer.es.js +27608 -0
  2. package/dist/bundles/remoteAiChatDrawer.umd.js +198 -0
  3. package/dist/cjs/bundles/RemoteAiChatDrawer/RemoteAiChatDrawer.d.ts +28 -0
  4. package/dist/cjs/bundles/RemoteAiChatDrawer/RemoteAiChatDrawer.js +44 -0
  5. package/dist/cjs/bundles/RemoteAiChatDrawer/RemoteAiChatDrawer.stories.d.ts +6 -0
  6. package/dist/cjs/bundles/RemoteAiChatDrawer/RemoteAiChatDrawer.stories.js +69 -0
  7. package/dist/cjs/bundles/remoteAiChatDrawer.d.ts +3 -0
  8. package/dist/cjs/bundles/remoteAiChatDrawer.js +15 -0
  9. package/dist/cjs/components/AiChat/AiChat.js +5 -3
  10. package/dist/cjs/components/AiChat/AiChat.stories.js +4 -13
  11. package/dist/cjs/components/AiChat/AiChat.test.js +25 -17
  12. package/dist/cjs/components/AiChat/test-utils/api.d.ts +2 -0
  13. package/dist/{esm/components/AiChat/story-utils.js → cjs/components/AiChat/test-utils/api.js} +64 -51
  14. package/dist/cjs/components/Alert/Alert.d.ts +15 -0
  15. package/dist/cjs/components/Alert/Alert.js +62 -0
  16. package/dist/cjs/components/Alert/Alert.stories.d.ts +8 -0
  17. package/dist/cjs/components/Alert/Alert.stories.js +53 -0
  18. package/dist/esm/bundles/RemoteAiChatDrawer/RemoteAiChatDrawer.d.ts +28 -0
  19. package/dist/esm/bundles/RemoteAiChatDrawer/RemoteAiChatDrawer.js +41 -0
  20. package/dist/esm/bundles/RemoteAiChatDrawer/RemoteAiChatDrawer.stories.d.ts +6 -0
  21. package/dist/esm/bundles/RemoteAiChatDrawer/RemoteAiChatDrawer.stories.js +66 -0
  22. package/dist/esm/bundles/remoteAiChatDrawer.d.ts +3 -0
  23. package/dist/esm/bundles/remoteAiChatDrawer.js +12 -0
  24. package/dist/esm/components/AiChat/AiChat.js +5 -3
  25. package/dist/esm/components/AiChat/AiChat.stories.js +4 -13
  26. package/dist/esm/components/AiChat/AiChat.test.js +25 -17
  27. package/dist/esm/components/AiChat/test-utils/api.d.ts +2 -0
  28. package/dist/{cjs/components/AiChat/story-utils.js → esm/components/AiChat/test-utils/api.js} +61 -55
  29. package/dist/esm/components/Alert/Alert.d.ts +15 -0
  30. package/dist/esm/components/Alert/Alert.js +59 -0
  31. package/dist/esm/components/Alert/Alert.stories.d.ts +8 -0
  32. package/dist/esm/components/Alert/Alert.stories.js +50 -0
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +9 -1
  35. package/dist/bundles/aiChat.es.js +0 -24878
  36. package/dist/bundles/aiChat.umd.js +0 -125
  37. package/dist/cjs/bundles/aiChat.d.ts +0 -6
  38. package/dist/cjs/bundles/aiChat.js +0 -13
  39. package/dist/cjs/components/AiChat/story-utils.d.ts +0 -3
  40. package/dist/esm/bundles/aiChat.d.ts +0 -6
  41. package/dist/esm/bundles/aiChat.js +0 -10
  42. package/dist/esm/components/AiChat/story-utils.d.ts +0 -3
@@ -0,0 +1,41 @@
1
+ import * as React from "react";
2
+ import { AiChat } from "../../components/AiChat/AiChat";
3
+ import Drawer from "@mui/material/Drawer";
4
+ const AiChatDrawer = ({ messageOrigin, transformBody, className, }) => {
5
+ const [open, setOpen] = React.useState(false);
6
+ const [chatSettings, setChatSettings] = React.useState(null);
7
+ React.useEffect(() => {
8
+ const cb = (event) => {
9
+ if (event.origin !== messageOrigin) {
10
+ if (process.env.NODE_ENV === "development") {
11
+ console.warn(`AiChatDrawer: received message from unexpected origin: ${event.origin}`);
12
+ }
13
+ return;
14
+ }
15
+ if (event.data.type === "smoot-design::chat-open") {
16
+ setOpen(true);
17
+ setChatSettings(event.data.payload);
18
+ }
19
+ };
20
+ console.log("Attaching listener");
21
+ window.addEventListener("message", cb);
22
+ return () => {
23
+ window.removeEventListener("message", cb);
24
+ };
25
+ }, [messageOrigin]);
26
+ return (React.createElement(Drawer, { className: className, PaperProps: {
27
+ sx: {
28
+ width: "600px",
29
+ maxWidth: "100%",
30
+ boxSizing: "border-box",
31
+ padding: "24px 40px",
32
+ ".MitAiChat--title": {
33
+ paddingTop: "0px",
34
+ },
35
+ },
36
+ }, anchor: "right", open: open, onClose: () => setOpen(false) }, chatSettings ? (React.createElement(AiChat, Object.assign({}, chatSettings, { requestOpts: {
37
+ transformBody,
38
+ apiUrl: chatSettings === null || chatSettings === void 0 ? void 0 : chatSettings.apiUrl,
39
+ }, onClose: () => setOpen(false) }))) : null));
40
+ };
41
+ export { AiChatDrawer };
@@ -0,0 +1,6 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { AiChatDrawer } from "./RemoteAiChatDrawer";
3
+ declare const meta: Meta<typeof AiChatDrawer>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof AiChatDrawer>;
6
+ export declare const StreamingResponses: Story;
@@ -0,0 +1,66 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks */
2
+ import * as React from "react";
3
+ import { AiChatDrawer } from "./RemoteAiChatDrawer";
4
+ import invariant from "tiny-invariant";
5
+ import { handlers } from "../../components/AiChat/test-utils/api";
6
+ const TEST_API_STREAMING = "http://localhost:4567/streaming";
7
+ const INITIAL_MESSAGES = [
8
+ {
9
+ content: "Hi! What are you interested in learning about?",
10
+ role: "assistant",
11
+ },
12
+ ];
13
+ const STARTERS = [
14
+ { content: "I'm interested in quantum computing" },
15
+ { content: "I want to understand global warming. " },
16
+ { content: "I am curious about AI applications for business" },
17
+ ];
18
+ const meta = {
19
+ title: "smoot-design/AI/RemoteAiChatDrawer",
20
+ component: AiChatDrawer,
21
+ render: () => {
22
+ return (React.createElement(React.Fragment, null,
23
+ React.createElement("iframe", { width: "600px", height: "300px", ref: (el) => {
24
+ var _a;
25
+ if (!el)
26
+ return;
27
+ const doc = el.contentDocument;
28
+ const parent = (_a = el.contentWindow) === null || _a === void 0 ? void 0 : _a.parent;
29
+ invariant(doc && parent);
30
+ const button = doc.createElement("button");
31
+ button.textContent = "Trigger chat (Send message to parent)";
32
+ doc.body.appendChild(button);
33
+ const div = doc.createElement("div");
34
+ doc.body.appendChild(div);
35
+ const label = doc.createElement("label");
36
+ label.textContent = "Message Data:";
37
+ div.appendChild(label);
38
+ const textarea = doc.createElement("textarea");
39
+ div.append(textarea);
40
+ textarea.style["display"] = "block";
41
+ textarea.style["width"] = "100%";
42
+ textarea.style["height"] = "225px";
43
+ const message = {
44
+ type: "smoot-design::chat-open",
45
+ payload: {
46
+ askTimTitle: "for help with Problem: Derivatives 1.1",
47
+ apiUrl: TEST_API_STREAMING,
48
+ initialMessages: INITIAL_MESSAGES,
49
+ conversationStarters: STARTERS,
50
+ },
51
+ };
52
+ textarea.value = JSON.stringify(message, null, 2);
53
+ button.addEventListener("click", () => {
54
+ parent.postMessage(JSON.parse(textarea.value));
55
+ });
56
+ }, title: "button frame" }),
57
+ React.createElement(AiChatDrawer, { messageOrigin: "http://localhost:6006" })));
58
+ },
59
+ parameters: {
60
+ msw: {
61
+ handlers,
62
+ },
63
+ },
64
+ };
65
+ export default meta;
66
+ export const StreamingResponses = {};
@@ -0,0 +1,3 @@
1
+ import type { AiChatDrawerProps } from "./RemoteAiChatDrawer/RemoteAiChatDrawer";
2
+ declare const init: (opts: AiChatDrawerProps) => void;
3
+ export { init };
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { AiChatDrawer } from "./RemoteAiChatDrawer/RemoteAiChatDrawer";
4
+ import { ThemeProvider } from "../components/ThemeProvider/ThemeProvider";
5
+ const init = (opts) => {
6
+ const root = document.createElement("div");
7
+ root.id = "smoot-chat-drawer-root";
8
+ document.body.append(root);
9
+ createRoot(root).render(React.createElement(ThemeProvider, null,
10
+ React.createElement(AiChatDrawer, Object.assign({}, opts))));
11
+ };
12
+ export { init };
@@ -21,6 +21,7 @@ import classNames from "classnames";
21
21
  import { SrAnnouncer } from "../SrAnnouncer/SrAnnouncer";
22
22
  import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden";
23
23
  import Typography from "@mui/material/Typography";
24
+ import { Alert } from "../Alert/Alert";
24
25
  const classes = {
25
26
  root: "MitAiChat--root",
26
27
  title: "MitAiChat--title",
@@ -162,7 +163,7 @@ const AiChatInternal = function AiChat(_a) {
162
163
  const prefix = Math.random().toString().slice(2);
163
164
  return initMsgs.map((m, i) => (Object.assign(Object.assign({}, m), { id: `initial-${prefix}-${i}` })));
164
165
  }, [initMsgs]);
165
- const { messages: unparsed, input, handleInputChange, handleSubmit, append, isLoading, stop, } = useAiChat(requestOpts, {
166
+ const { messages: unparsed, input, handleInputChange, handleSubmit, append, isLoading, stop, error, } = useAiChat(requestOpts, {
166
167
  initialMessages: initialMessages,
167
168
  id: chatId,
168
169
  });
@@ -178,7 +179,7 @@ const AiChatInternal = function AiChat(_a) {
178
179
  });
179
180
  }, [parseContent, unparsed, initialMessages]);
180
181
  const showStarters = messages.length === initialMessages.length;
181
- const waiting = !showStarters && ((_b = messages[messages.length - 1]) === null || _b === void 0 ? void 0 : _b.role) === "user";
182
+ const waiting = !showStarters && !error && ((_b = messages[messages.length - 1]) === null || _b === void 0 ? void 0 : _b.role) === "user";
182
183
  const stoppable = isLoading && ((_c = messages[messages.length - 1]) === null || _c === void 0 ? void 0 : _c.role) !== "user";
183
184
  const scrollToBottom = () => {
184
185
  var _a;
@@ -204,7 +205,8 @@ const AiChatInternal = function AiChat(_a) {
204
205
  } }, m.content))))) : null,
205
206
  waiting ? (React.createElement(MessageRow, { className: classNames(classes.messageRow, classes.messageRowAssistant), key: "loading" },
206
207
  React.createElement(Message, null,
207
- React.createElement(RiMoreFill, null)))) : null),
208
+ React.createElement(RiMoreFill, null)))) : null,
209
+ error ? (React.createElement(Alert, { severity: "error", closable: true }, "An unexpected error has occurred.")) : null),
208
210
  React.createElement("form", { onSubmit: (e) => {
209
211
  e.preventDefault();
210
212
  if (isLoading && stoppable) {
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { AiChat } from "./AiChat";
3
- import { mockJson, mockStreaming } from "./story-utils";
4
3
  import styled from "@emotion/styled";
5
4
  import { fn } from "@storybook/test";
5
+ import { handlers } from "./test-utils/api";
6
6
  const TEST_API_STREAMING = "http://localhost:4567/streaming";
7
7
  const TEST_API_JSON = "http://localhost:4567/json";
8
8
  const INITIAL_MESSAGES = [
@@ -31,6 +31,9 @@ const Container = styled.div({
31
31
  const meta = {
32
32
  title: "smoot-design/AI/AiChat",
33
33
  component: AiChat,
34
+ parameters: {
35
+ msw: { handlers },
36
+ },
34
37
  render: (args) => React.createElement(AiChat, Object.assign({}, args)),
35
38
  decorators: (Story) => {
36
39
  return (React.createElement(Container, null,
@@ -55,18 +58,6 @@ const meta = {
55
58
  table: { readonly: true }, // See above
56
59
  },
57
60
  },
58
- beforeEach: () => {
59
- const originalFetch = window.fetch;
60
- window.fetch = (url, opts) => {
61
- if (url === TEST_API_STREAMING) {
62
- return mockStreaming();
63
- }
64
- else if (url === TEST_API_JSON) {
65
- return mockJson();
66
- }
67
- return originalFetch(url, opts);
68
- };
69
- },
70
61
  };
71
62
  export default meta;
72
63
  export const StreamingResponses = {};
@@ -15,17 +15,18 @@ import { AiChat } from "./AiChat";
15
15
  import { ThemeProvider } from "../ThemeProvider/ThemeProvider";
16
16
  import * as React from "react";
17
17
  import { faker } from "@faker-js/faker/locale/en";
18
+ import { http, HttpResponse } from "msw";
19
+ import { setupServer } from "msw/node";
18
20
  const counter = jest.fn(); // use jest.fn as counter because it resets on each test
19
- const mockFetch = jest.mocked(jest.fn(() => {
21
+ const API_URL = "http://localhost:4567/test";
22
+ const server = setupServer(http.post(API_URL, () => __awaiter(void 0, void 0, void 0, function* () {
20
23
  const count = counter.mock.calls.length;
21
24
  counter();
22
- return Promise.resolve(new Response(`AI Response ${count}`, {
23
- headers: {
24
- "Content-Type": "application/json",
25
- },
26
- }));
27
- }));
28
- window.fetch = mockFetch;
25
+ return HttpResponse.text(`AI Response ${count}`);
26
+ })));
27
+ beforeAll(() => server.listen());
28
+ afterEach(() => server.resetHandlers());
29
+ afterAll(() => server.close());
29
30
  jest.mock("react-markdown", () => {
30
31
  return {
31
32
  __esModule: true,
@@ -58,9 +59,9 @@ describe("AiChat", () => {
58
59
  { content: faker.lorem.sentence() },
59
60
  { content: faker.lorem.sentence() },
60
61
  ];
61
- const view = render(React.createElement(AiChat, Object.assign({ "data-testid": "ai-chat", initialMessages: initialMessages, conversationStarters: conversationStarters, requestOpts: { apiUrl: "http://localhost:4567/test" }, placeholder: "Type a message..." }, props)), { wrapper: ThemeProvider });
62
+ const view = render(React.createElement(AiChat, Object.assign({ "data-testid": "ai-chat", initialMessages: initialMessages, conversationStarters: conversationStarters, requestOpts: { apiUrl: API_URL }, placeholder: "Type a message..." }, props)), { wrapper: ThemeProvider });
62
63
  const rerender = (newProps) => {
63
- view.rerender(React.createElement(AiChat, Object.assign({ "data-testid": "ai-chat", initialMessages: initialMessages, conversationStarters: conversationStarters, requestOpts: { apiUrl: "http://localhost:4567/test" } }, newProps)));
64
+ view.rerender(React.createElement(AiChat, Object.assign({ "data-testid": "ai-chat", initialMessages: initialMessages, conversationStarters: conversationStarters, requestOpts: { apiUrl: API_URL } }, newProps)));
64
65
  };
65
66
  return { initialMessages, conversationStarters, rerender };
66
67
  };
@@ -106,11 +107,11 @@ describe("AiChat", () => {
106
107
  yield whenCount(getMessages, 3);
107
108
  }));
108
109
  test("transformBody is called before sending requests", () => __awaiter(void 0, void 0, void 0, function* () {
110
+ const mockFetch = jest.spyOn(window, "fetch");
109
111
  const fakeBody = { message: faker.lorem.sentence() };
110
- const apiUrl = faker.internet.url();
111
112
  const transformBody = jest.fn(() => fakeBody);
112
113
  const { initialMessages } = setup({
113
- requestOpts: { apiUrl, transformBody },
114
+ requestOpts: { apiUrl: API_URL, transformBody },
114
115
  });
115
116
  yield user.click(screen.getByPlaceholderText("Type a message..."));
116
117
  yield user.paste("User message");
@@ -120,16 +121,15 @@ describe("AiChat", () => {
120
121
  expect.objectContaining({ content: "User message", role: "user" }),
121
122
  ]);
122
123
  expect(mockFetch).toHaveBeenCalledTimes(1);
123
- expect(mockFetch).toHaveBeenCalledWith(apiUrl, expect.objectContaining({
124
+ expect(mockFetch).toHaveBeenCalledWith(API_URL, expect.objectContaining({
124
125
  body: JSON.stringify(fakeBody),
125
126
  }));
126
127
  }));
127
128
  test("parseContent is called on the API-received message content", () => __awaiter(void 0, void 0, void 0, function* () {
128
129
  const fakeBody = { message: faker.lorem.sentence() };
129
- const apiUrl = faker.internet.url();
130
130
  const transformBody = jest.fn(() => fakeBody);
131
131
  const { initialMessages, conversationStarters } = setup({
132
- requestOpts: { apiUrl, transformBody },
132
+ requestOpts: { apiUrl: API_URL, transformBody },
133
133
  parseContent: jest.fn((content) => `Parsed: ${content}`),
134
134
  });
135
135
  yield user.click(getConversationStarters()[0]);
@@ -149,12 +149,20 @@ describe("AiChat", () => {
149
149
  }));
150
150
  test("Passes extra attributes to root", () => {
151
151
  const fakeBody = { message: faker.lorem.sentence() };
152
- const apiUrl = faker.internet.url();
153
152
  const transformBody = jest.fn(() => fakeBody);
154
153
  setup({
155
- requestOpts: { apiUrl, transformBody },
154
+ requestOpts: { apiUrl: API_URL, transformBody },
156
155
  parseContent: jest.fn((content) => `Parsed: ${content}`),
157
156
  });
158
157
  expect(screen.getByTestId("ai-chat")).toBeInTheDocument();
159
158
  });
159
+ test("If the API returns an error, an alert is shown", () => __awaiter(void 0, void 0, void 0, function* () {
160
+ setup();
161
+ server.use(http.post(API_URL, () => __awaiter(void 0, void 0, void 0, function* () {
162
+ return new HttpResponse(null, { status: 500 });
163
+ })));
164
+ yield user.click(getConversationStarters()[0]);
165
+ const alert = yield screen.findByRole("alert");
166
+ expect(alert).toHaveTextContent("An unexpected error has occurred");
167
+ }));
160
168
  });
@@ -0,0 +1,2 @@
1
+ declare const handlers: import("msw").HttpHandler[];
2
+ export { handlers };
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
2
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
3
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -8,8 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
8
  });
10
9
  };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.mockJson = exports.mockStreaming = void 0;
10
+ import { http, HttpResponse, delay } from "msw";
13
11
  const SAMPLE_RESPONSES = [
14
12
  `For exploring AI applications in business, I recommend the following course from MIT:
15
13
 
@@ -45,59 +43,67 @@ const rand = (min, max) => {
45
43
  // min and max included
46
44
  return Math.floor(Math.random() * (max - min + 1) + min);
47
45
  };
48
- const mockStreaming = function mockApi() {
49
- return __awaiter(this, void 0, void 0, function* () {
50
- let timerId;
51
- const response = SAMPLE_RESPONSES[rand(0, SAMPLE_RESPONSES.length - 1)];
52
- const chunks = response.split(" ").reduce((acc, word) => {
53
- const last = acc[acc.length - 1];
54
- if (acc.length === 0) {
55
- acc.push(word);
56
- }
57
- else if (Math.random() < 0.75) {
58
- acc[acc.length - 1] = `${last} ${word}`;
59
- }
60
- else {
61
- acc.push(` ${word}`);
62
- }
63
- return acc;
64
- }, []);
65
- const num = chunks.length;
66
- let i = 0;
67
- yield new Promise((resolve) => setTimeout(resolve, 800));
68
- const body = new ReadableStream({
69
- start(controller) {
70
- timerId = setInterval(() => {
71
- const msg = new TextEncoder().encode(chunks[i]);
72
- controller.enqueue(msg);
73
- i++;
74
- if (i === num) {
75
- controller.close();
76
- clearInterval(timerId);
77
- }
78
- }, 100);
79
- },
80
- cancel() {
81
- if (timerId) {
46
+ const getReadableStream = () => {
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
+ return new ReadableStream({
65
+ start(controller) {
66
+ timerId = setInterval(() => {
67
+ const msg = new TextEncoder().encode(chunks[i]);
68
+ controller.enqueue(msg);
69
+ i++;
70
+ if (i === num) {
71
+ controller.close();
82
72
  clearInterval(timerId);
83
73
  }
84
- },
85
- });
86
- return Promise.resolve(new Response(body, {
87
- headers: {
88
- "Content-Type": "text/event-stream",
89
- },
90
- }));
74
+ }, 100);
75
+ },
76
+ cancel() {
77
+ if (timerId) {
78
+ clearInterval(timerId);
79
+ }
80
+ },
91
81
  });
92
82
  };
93
- exports.mockStreaming = mockStreaming;
94
- const mockJson = () => __awaiter(void 0, void 0, void 0, function* () {
95
- const message = SAMPLE_RESPONSES[rand(0, SAMPLE_RESPONSES.length - 1)];
96
- yield new Promise((res) => setTimeout(res, 1000));
97
- return Promise.resolve(new Response(JSON.stringify({ message }), {
98
- headers: {
99
- "Content-Type": "application/json",
100
- },
101
- }));
102
- });
103
- exports.mockJson = mockJson;
83
+ const handlers = [
84
+ http.post("http://localhost:4567/streaming", (_a) => __awaiter(void 0, [_a], void 0, function* ({ request }) {
85
+ yield delay(600);
86
+ const body = getReadableStream();
87
+ const requestBody = yield request.json();
88
+ if (Array.isArray(requestBody)) {
89
+ const last = requestBody[requestBody.length - 1];
90
+ const { content } = last;
91
+ if (content === "error") {
92
+ return new HttpResponse("Internal Server Error", {
93
+ status: 500,
94
+ });
95
+ }
96
+ }
97
+ return new HttpResponse(body, {
98
+ headers: {
99
+ "Content-Type": "text/plain",
100
+ },
101
+ });
102
+ })),
103
+ http.post("http://localhost:4567/json", () => __awaiter(void 0, void 0, void 0, function* () {
104
+ const message = SAMPLE_RESPONSES[rand(0, SAMPLE_RESPONSES.length - 1)];
105
+ yield delay(800);
106
+ return HttpResponse.json({ message });
107
+ })),
108
+ ];
109
+ export { 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,59 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import styled from "@emotion/styled";
4
+ import { default as MuiAlert } from "@mui/material/Alert";
5
+ const getColor = (theme, severity) => {
6
+ return {
7
+ info: theme.custom.colors.blue,
8
+ success: theme.custom.colors.green,
9
+ warning: theme.custom.colors.orange,
10
+ error: theme.custom.colors.lightRed,
11
+ }[severity];
12
+ };
13
+ const AlertStyled = styled(MuiAlert)(({ theme, severity }) => ({
14
+ padding: "11px 16px",
15
+ borderRadius: 4,
16
+ borderWidth: 2,
17
+ borderStyle: "solid",
18
+ borderColor: getColor(theme, severity),
19
+ background: "#FFF",
20
+ ".MuiAlert-message": Object.assign(Object.assign({}, theme.typography.body2), { color: theme.custom.colors.darkGray2, alignSelf: "center" }),
21
+ "> div": {
22
+ paddingTop: 0,
23
+ paddingBottom: 0,
24
+ },
25
+ ".MuiAlert-icon": {
26
+ marginRight: 8,
27
+ svg: {
28
+ width: 16,
29
+ fill: getColor(theme, severity),
30
+ },
31
+ },
32
+ button: {
33
+ padding: 0,
34
+ ":hover": {
35
+ margin: 0,
36
+ background: "none",
37
+ },
38
+ },
39
+ }));
40
+ const Hidden = styled.span({ display: "none" });
41
+ const Alert = ({ visible = true, severity = "info", closable, children, className, }) => {
42
+ const [_visible, setVisible] = React.useState(visible);
43
+ const id = React.useId();
44
+ const onCloseClick = () => {
45
+ setVisible(false);
46
+ };
47
+ React.useEffect(() => {
48
+ setVisible(visible);
49
+ }, [visible]);
50
+ if (!_visible) {
51
+ return null;
52
+ }
53
+ return (React.createElement(AlertStyled, { severity: severity, onClose: closable ? onCloseClick : undefined, role: "alert", "aria-describedby": id, className: className },
54
+ children,
55
+ React.createElement(Hidden, { id: id },
56
+ severity,
57
+ " message")));
58
+ };
59
+ export { 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,50 @@
1
+ import * as React from "react";
2
+ import { Alert } from "./Alert";
3
+ import Stack from "@mui/material/Stack";
4
+ const meta = {
5
+ title: "smoot-design/Alert",
6
+ component: Alert,
7
+ };
8
+ export default meta;
9
+ export const Basic = {
10
+ args: {
11
+ severity: "info",
12
+ },
13
+ render: (args) => (React.createElement(Alert, Object.assign({}, args),
14
+ "Alert with severity \"",
15
+ args.severity,
16
+ "\"")),
17
+ };
18
+ export const Closable = {
19
+ args: {
20
+ severity: "warning",
21
+ closable: true,
22
+ },
23
+ render: (args) => (React.createElement(Alert, Object.assign({}, args),
24
+ "Closable alert with severity \"",
25
+ args.severity,
26
+ "\"")),
27
+ };
28
+ export const Variants = {
29
+ argTypes: {
30
+ severity: {
31
+ table: {
32
+ disable: true,
33
+ },
34
+ },
35
+ closable: {
36
+ table: {
37
+ disable: true,
38
+ },
39
+ },
40
+ },
41
+ render: (args) => (React.createElement(Stack, { direction: "column", gap: 2, sx: { my: 2 } },
42
+ React.createElement(Alert, Object.assign({}, args, { severity: "info" }), "Alert with severity \"info\""),
43
+ React.createElement(Alert, Object.assign({}, args, { closable: true, severity: "info" }), "Closable alert with severity \"info\""),
44
+ React.createElement(Alert, Object.assign({}, args, { severity: "success" }), "Alert with severity \"success\""),
45
+ React.createElement(Alert, Object.assign({}, args, { closable: true, severity: "success" }), "Closable alert with severity \"success\""),
46
+ React.createElement(Alert, Object.assign({}, args, { severity: "warning" }), "Alert with severity \"warning\""),
47
+ React.createElement(Alert, Object.assign({}, args, { closable: true, severity: "warning" }), "Closable alert with severity \"warning\""),
48
+ React.createElement(Alert, Object.assign({}, args, { severity: "error" }), "Alert with severity \"error\""),
49
+ React.createElement(Alert, Object.assign({}, args, { closable: true, severity: "error" }), "Closable alert with severity \"error\""))),
50
+ };