@yak-io/react 0.4.0 → 0.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.
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import type React from "react";
2
2
  import { type Theme, type ChatConfigProvider, type ToolCallHandler, type GraphQLSchemaHandler, type RESTSchemaHandler } from "@yak-io/javascript";
3
3
  /**
4
4
  * Props for YakProvider
@@ -1 +1 @@
1
- {"version":3,"file":"YakProvider.d.ts","sourceRoot":"","sources":["../src/YakProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAEjF,OAAO,EAEL,KAAK,KAAK,EAGV,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAEpB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAG5B;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;OAGG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;OAEG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IAC3C;;OAEG;IACH,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,mCAAmC;IACnC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,qEAAqE;IACrE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,0BAA0B;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAmMF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,EACL,UAAU,EACV,oBAAoB,EACpB,QAAQ,GACT,EAAE,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAwUtC"}
1
+ {"version":3,"file":"YakProvider.d.ts","sourceRoot":"","sources":["../src/YakProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAEL,KAAK,KAAK,EAGV,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAEpB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAG5B;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;OAGG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;OAEG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IAC3C;;OAEG;IACH,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,mCAAmC;IACnC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,qEAAqE;IACrE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,0BAA0B;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAmMF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,EACL,UAAU,EACV,oBAAoB,EACpB,QAAQ,GACT,EAAE,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAqVtC"}
@@ -260,7 +260,18 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
260
260
  chatConfig: chatConfig ?? undefined,
261
261
  onToolCallComplete: handleToolCallComplete,
262
262
  });
263
- }, [appId, onToolCall, onGraphQLSchemaCall, onRESTSchemaCall, theme, onRedirect, disableRestartButton, chatConfig, client, handleToolCallComplete]);
263
+ }, [
264
+ appId,
265
+ onToolCall,
266
+ onGraphQLSchemaCall,
267
+ onRESTSchemaCall,
268
+ theme,
269
+ onRedirect,
270
+ disableRestartButton,
271
+ chatConfig,
272
+ client,
273
+ handleToolCallComplete,
274
+ ]);
264
275
  // Update client iframe window
265
276
  useEffect(() => {
266
277
  client.setIframeWindow(iframeWindow);
@@ -320,7 +331,10 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
320
331
  return;
321
332
  const mobileQuery = window.matchMedia("(max-width: 640px)");
322
333
  const notifyFullscreen = (isFullscreen) => {
323
- const msg = { type: "yak:viewport", payload: { fullscreen: isFullscreen } };
334
+ const msg = {
335
+ type: "yak:viewport",
336
+ payload: { fullscreen: isFullscreen },
337
+ };
324
338
  iframeWindow?.postMessage(msg, iframeOrigin);
325
339
  };
326
340
  // Send initial state
@@ -450,10 +464,8 @@ export function YakProvider({ appId, getConfig, onToolCall, onGraphQLSchemaCall,
450
464
  // Determine color mode class for the container
451
465
  const colorModeClass = colorMode === "light" ? "yak-panel-light" : colorMode === "dark" ? "yak-panel-dark" : "";
452
466
  // Build container class names
453
- const containerClasses = [
454
- "yak-panel-container",
455
- isDrawer && "yak-panel-drawer",
456
- colorModeClass,
457
- ].filter(Boolean).join(" ");
467
+ const containerClasses = ["yak-panel-container", isDrawer && "yak-panel-drawer", colorModeClass]
468
+ .filter(Boolean)
469
+ .join(" ");
458
470
  return (_jsxs(YakContext.Provider, { value: contextValue, children: [children, _jsx("style", { children: getIframeStyles() }), hasBeenOpened && (_jsx("div", { className: "yak-panel-root", "data-expanded": isExpanded, children: _jsx("div", { className: containerClasses, "data-position": position, "data-open": isWidgetOpen && isIframeReady, "data-expanded": isExpanded, children: _jsx("iframe", { ref: iframeRef, src: iframeSrc, className: "yak-panel-iframe", title: "yak-chat-host" }) }) }))] }));
459
471
  }
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import type React from "react";
2
2
  import type { WidgetPosition } from "@yak-io/javascript";
3
3
  /**
4
4
  * Props for YakWidget
@@ -1 +1 @@
1
- {"version":3,"file":"YakWidget.d.ts","sourceRoot":"","sources":["../src/YakWidget.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAiPzD;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,0BAA0B;IAC1B,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACxC,0CAA0C;IAC1C,WAAW,CAAC,EAAE;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,yCAAyC;IACzC,UAAU,CAAC,EAAE;QACX,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAEF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACxB,YAA4B,EAC5B,QAAyB,EACzB,SAAS,EACT,WAAW,EACX,UAAU,GACX,GAAE,cAAmB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAkEzC"}
1
+ {"version":3,"file":"YakWidget.d.ts","sourceRoot":"","sources":["../src/YakWidget.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AA4NzD;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,0BAA0B;IAC1B,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACxC,0CAA0C;IAC1C,WAAW,CAAC,EAAE;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,yCAAyC;IACzC,UAAU,CAAC,EAAE;QACX,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAwCF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACxB,YAA4B,EAC5B,QAAyB,EACzB,SAAS,EACT,WAAW,EACX,UAAU,GACX,GAAE,cAAmB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAyCzC"}
package/dist/YakWidget.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useYak } from "./context.js";
3
+ import { useContext } from "react";
4
+ import { YakContext, useYak } from "./context.js";
4
5
  /**
5
6
  * All button styles consolidated in one place
6
7
  */
@@ -99,6 +100,19 @@ function getButtonStyles() {
99
100
  color: currentColor;
100
101
  }
101
102
 
103
+ /* Invert logo for dark mode */
104
+ @media (prefers-color-scheme: dark) {
105
+ .yak-widget-trigger:not(.yak-widget-light) .yak-widget-icon {
106
+ filter: invert(1);
107
+ }
108
+ }
109
+ .yak-widget-trigger.yak-widget-dark .yak-widget-icon {
110
+ filter: invert(1);
111
+ }
112
+ .yak-widget-trigger.yak-widget-light .yak-widget-icon {
113
+ filter: none;
114
+ }
115
+
102
116
  /* Spinner animation for loading state */
103
117
  .yak-widget-spinner {
104
118
  width: 20px;
@@ -206,25 +220,29 @@ function getButtonStyles() {
206
220
  `;
207
221
  }
208
222
  /**
209
- * Inline SVG for brain/circuit fallback icon
223
+ * Compute button CSS variables from custom button color props
210
224
  */
211
- function BrainCircuitIcon({ size = 20, className }) {
212
- return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: [_jsx("path", { d: "M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z" }), _jsx("path", { d: "M9 13a4.5 4.5 0 0 0 3-4" }), _jsx("path", { d: "M6.003 5.125A3 3 0 0 0 6.401 6.5" }), _jsx("path", { d: "M3.477 10.896a4 4 0 0 1 .585-.396" }), _jsx("path", { d: "M6 18a4 4 0 0 1-1.967-.516" }), _jsx("path", { d: "M12 13h4" }), _jsx("path", { d: "M12 18h6a2 2 0 0 1 2 2v1" }), _jsx("path", { d: "M12 8h8" }), _jsx("path", { d: "M16 8V5a2 2 0 0 1 2-2" }), _jsx("circle", { cx: "16", cy: "13", r: ".5" }), _jsx("circle", { cx: "18", cy: "3", r: ".5" }), _jsx("circle", { cx: "20", cy: "21", r: ".5" }), _jsx("circle", { cx: "20", cy: "8", r: ".5" })] }));
225
+ function buildButtonStyle(lightButton, darkButton) {
226
+ const style = {};
227
+ if (lightButton?.background)
228
+ style["--yak-btn-light-bg"] = lightButton.background;
229
+ if (lightButton?.color)
230
+ style["--yak-btn-light-color"] = lightButton.color;
231
+ if (lightButton?.border)
232
+ style["--yak-btn-light-border"] = lightButton.border;
233
+ if (darkButton?.background)
234
+ style["--yak-btn-dark-bg"] = darkButton.background;
235
+ if (darkButton?.color)
236
+ style["--yak-btn-dark-color"] = darkButton.color;
237
+ if (darkButton?.border)
238
+ style["--yak-btn-dark-border"] = darkButton.border;
239
+ return style;
213
240
  }
214
241
  /**
215
- * YakWidget renders a fixed-position launcher button.
216
- * The chat iframe is rendered by YakProvider - this is just the trigger button.
242
+ * Compute button class names from color mode and custom theme flags
217
243
  */
218
- export function YakWidget({ triggerLabel = "Ask with AI", position = "bottom-right", colorMode, lightButton, darkButton, } = {}) {
219
- const { open, isOpen, isIframeReady } = useYak();
220
- // Track if we're in a loading state (open but iframe not ready)
221
- const isLoading = isOpen && !isIframeReady;
222
- // Check if custom button theme is provided
223
- const hasLightCustom = lightButton?.background || lightButton?.color || lightButton?.border;
224
- const hasDarkCustom = darkButton?.background || darkButton?.color || darkButton?.border;
225
- // Determine color mode class for the widget
244
+ function buildButtonClasses(colorMode, hasLightCustom, hasDarkCustom) {
226
245
  const colorModeClass = colorMode === "light" ? "yak-widget-light" : colorMode === "dark" ? "yak-widget-dark" : "";
227
- // Determine which custom class to apply based on color mode
228
246
  let customButtonClass = "";
229
247
  if (colorMode === "light" && hasLightCustom) {
230
248
  customButtonClass = "yak-widget-custom-light";
@@ -232,25 +250,20 @@ export function YakWidget({ triggerLabel = "Ask with AI", position = "bottom-rig
232
250
  else if (colorMode === "dark" && hasDarkCustom) {
233
251
  customButtonClass = "yak-widget-custom-dark";
234
252
  }
235
- // Build inline style for button CSS variables
236
- const buttonStyle = {};
237
- if (lightButton?.background)
238
- buttonStyle["--yak-btn-light-bg"] = lightButton.background;
239
- if (lightButton?.color)
240
- buttonStyle["--yak-btn-light-color"] = lightButton.color;
241
- if (lightButton?.border)
242
- buttonStyle["--yak-btn-light-border"] = lightButton.border;
243
- if (darkButton?.background)
244
- buttonStyle["--yak-btn-dark-bg"] = darkButton.background;
245
- if (darkButton?.color)
246
- buttonStyle["--yak-btn-dark-color"] = darkButton.color;
247
- if (darkButton?.border)
248
- buttonStyle["--yak-btn-dark-border"] = darkButton.border;
249
- // Build button class names
250
- const buttonClasses = [
251
- "yak-widget-trigger",
252
- colorModeClass,
253
- customButtonClass,
254
- ].filter(Boolean).join(" ");
255
- return (_jsxs(_Fragment, { children: [_jsx("style", { children: getButtonStyles() }), _jsxs("button", { onClick: open, className: buttonClasses, style: Object.keys(buttonStyle).length > 0 ? buttonStyle : undefined, "data-position": position, "data-has-light-custom": hasLightCustom || undefined, "data-has-dark-custom": hasDarkCustom || undefined, "aria-label": isLoading ? "Loading chat" : "Open chat", disabled: isLoading, children: [_jsx("span", { className: "yak-widget-trigger-label", children: triggerLabel }), _jsx("div", { className: "yak-widget-icon-bg", children: isLoading ? (_jsx("div", { className: "yak-widget-spinner", "aria-hidden": "true" })) : (_jsx(BrainCircuitIcon, { size: 20, className: "yak-widget-icon" })) })] })] }));
253
+ return ["yak-widget-trigger", colorModeClass, customButtonClass].filter(Boolean).join(" ");
254
+ }
255
+ /**
256
+ * YakWidget renders a fixed-position launcher button.
257
+ * The chat iframe is rendered by YakProvider - this is just the trigger button.
258
+ */
259
+ export function YakWidget({ triggerLabel = "Ask with AI", position = "bottom-right", colorMode, lightButton, darkButton, } = {}) {
260
+ const { open, isOpen, isIframeReady } = useYak();
261
+ const internal = useContext(YakContext);
262
+ const logoUrl = internal ? `${internal.getIframeOrigin()}/logo.svg` : "";
263
+ const isLoading = isOpen && !isIframeReady;
264
+ const hasLightCustom = lightButton?.background || lightButton?.color || lightButton?.border;
265
+ const hasDarkCustom = darkButton?.background || darkButton?.color || darkButton?.border;
266
+ const buttonStyle = buildButtonStyle(lightButton, darkButton);
267
+ const buttonClasses = buildButtonClasses(colorMode, hasLightCustom, hasDarkCustom);
268
+ return (_jsxs(_Fragment, { children: [_jsx("style", { children: getButtonStyles() }), _jsxs("button", { type: "button", onClick: open, className: buttonClasses, style: Object.keys(buttonStyle).length > 0 ? buttonStyle : undefined, "data-position": position, "data-has-light-custom": hasLightCustom || undefined, "data-has-dark-custom": hasDarkCustom || undefined, "aria-label": isLoading ? "Loading chat" : "Open chat", disabled: isLoading, children: [_jsx("span", { className: "yak-widget-trigger-label", children: triggerLabel }), _jsx("div", { className: "yak-widget-icon-bg", children: isLoading ? (_jsx("div", { className: "yak-widget-spinner", "aria-hidden": "true" })) : (_jsx("img", { src: logoUrl, alt: "", width: 20, height: 20, className: "yak-widget-icon" })) })] })] }));
256
269
  }
package/dist/index.d.ts CHANGED
@@ -4,5 +4,5 @@ export { YakProvider } from "./YakProvider.js";
4
4
  export type { YakProviderProps } from "./YakProvider.js";
5
5
  export { YakWidget } from "./YakWidget.js";
6
6
  export type { YakWidgetProps } from "./YakWidget.js";
7
- export { type GraphQLSchemaHandler, type RESTSchemaHandler, type GraphQLRequest, type RESTRequest, type ToolCallHandler, type ToolCallEvent, type SchemaSource, type GraphQLSchemaSource, type OpenAPISchemaSource, type Theme, type ThemeColors, type ButtonColors, type WidgetPosition, } from "@yak-io/javascript";
7
+ export type { GraphQLSchemaHandler, RESTSchemaHandler, GraphQLRequest, RESTRequest, ToolCallHandler, ToolCallEvent, SchemaSource, GraphQLSchemaSource, OpenAPISchemaSource, Theme, ThemeColors, ButtonColors, WidgetPosition, } from "@yak-io/javascript";
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACvD,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,cAAc,GACpB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACvD,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,eAAe,EACf,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,EACL,WAAW,EACX,YAAY,EACZ,cAAc,GACf,MAAM,oBAAoB,CAAC"}
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@yak-io/react",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "React SDK for embedding yak chatbot",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
7
7
  "author": "Yak <support@yak.io>",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/9f-au/yak.git",
10
+ "url": "https://github.com/388-labs/yak.git",
11
11
  "directory": "packages/react"
12
12
  },
13
13
  "publishConfig": {
@@ -41,21 +41,29 @@
41
41
  "./package.json": "./package.json"
42
42
  },
43
43
  "dependencies": {
44
- "@yak-io/javascript": "0.4.0"
44
+ "@yak-io/javascript": "0.4.1"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "react": "^18.0.0 || ^19.0.0",
48
48
  "react-dom": "^18.0.0 || ^19.0.0"
49
49
  },
50
50
  "devDependencies": {
51
+ "@testing-library/jest-dom": "^6.9.1",
52
+ "@testing-library/react": "^16.3.2",
51
53
  "@types/node": "^24.10.4",
52
- "@types/react": "^19.2.10",
54
+ "@types/react": "^19.2.14",
53
55
  "@types/react-dom": "^19.2.0",
56
+ "jsdom": "^28.1.0",
57
+ "react": "^19.2.4",
58
+ "react-dom": "^19.2.4",
54
59
  "typescript": "^5.3.0",
55
60
  "@repo/typescript-config": "0.0.0"
56
61
  },
57
62
  "scripts": {
58
63
  "build": "tsc",
59
- "check-types": "tsc --noEmit"
64
+ "check-types": "tsc --noEmit",
65
+ "test": "vitest run",
66
+ "lint": "biome lint ./src --fix",
67
+ "format": "biome format ./src --write"
60
68
  }
61
69
  }