@rustors/react 0.0.0-stage → 0.3.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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +85 -2
  3. package/dist/components/BoundingBox.d.ts +4 -0
  4. package/dist/components/BoundingBox.d.ts.map +1 -0
  5. package/dist/components/BoundingBox.js +40 -0
  6. package/dist/components/BoundingBox.js.map +1 -0
  7. package/dist/components/DropzonePreview.d.ts +4 -0
  8. package/dist/components/DropzonePreview.d.ts.map +1 -0
  9. package/dist/components/DropzonePreview.js +40 -0
  10. package/dist/components/DropzonePreview.js.map +1 -0
  11. package/dist/components/DropzonePrompt.d.ts +4 -0
  12. package/dist/components/DropzonePrompt.d.ts.map +1 -0
  13. package/dist/components/DropzonePrompt.js +16 -0
  14. package/dist/components/DropzonePrompt.js.map +1 -0
  15. package/dist/components/OcrDropzone.d.ts +4 -0
  16. package/dist/components/OcrDropzone.d.ts.map +1 -0
  17. package/dist/components/OcrDropzone.js +65 -0
  18. package/dist/components/OcrDropzone.js.map +1 -0
  19. package/dist/components/OcrOverlay.d.ts +4 -0
  20. package/dist/components/OcrOverlay.d.ts.map +1 -0
  21. package/dist/components/OcrOverlay.js +50 -0
  22. package/dist/components/OcrOverlay.js.map +1 -0
  23. package/dist/context/RustoContext.d.ts +3 -0
  24. package/dist/context/RustoContext.d.ts.map +1 -0
  25. package/dist/context/RustoContext.js +6 -0
  26. package/dist/context/RustoContext.js.map +1 -0
  27. package/dist/context/RustoProvider.d.ts +4 -0
  28. package/dist/context/RustoProvider.d.ts.map +1 -0
  29. package/dist/context/RustoProvider.js +41 -0
  30. package/dist/context/RustoProvider.js.map +1 -0
  31. package/dist/hooks/useOcr.d.ts +3 -0
  32. package/dist/hooks/useOcr.d.ts.map +1 -0
  33. package/dist/hooks/useOcr.js +59 -0
  34. package/dist/hooks/useOcr.js.map +1 -0
  35. package/dist/hooks/useRusto.d.ts +3 -0
  36. package/dist/hooks/useRusto.d.ts.map +1 -0
  37. package/dist/hooks/useRusto.js +13 -0
  38. package/dist/hooks/useRusto.js.map +1 -0
  39. package/dist/index.d.ts +15 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +54 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/services/ocrService.d.ts +12 -0
  44. package/dist/services/ocrService.d.ts.map +1 -0
  45. package/dist/services/ocrService.js +30 -0
  46. package/dist/services/ocrService.js.map +1 -0
  47. package/dist/types/components.d.ts +44 -0
  48. package/dist/types/components.d.ts.map +1 -0
  49. package/dist/types/components.js +3 -0
  50. package/dist/types/components.js.map +1 -0
  51. package/dist/types/context.d.ts +16 -0
  52. package/dist/types/context.d.ts.map +1 -0
  53. package/dist/types/context.js +3 -0
  54. package/dist/types/context.js.map +1 -0
  55. package/dist/types/hooks.d.ts +14 -0
  56. package/dist/types/hooks.d.ts.map +1 -0
  57. package/dist/types/hooks.js +3 -0
  58. package/dist/types/hooks.js.map +1 -0
  59. package/dist/types/index.d.ts +4 -0
  60. package/dist/types/index.d.ts.map +1 -0
  61. package/dist/types/index.js +20 -0
  62. package/dist/types/index.js.map +1 -0
  63. package/dist/utils/formatters.d.ts +14 -0
  64. package/dist/utils/formatters.d.ts.map +1 -0
  65. package/dist/utils/formatters.js +29 -0
  66. package/dist/utils/formatters.js.map +1 -0
  67. package/dist/utils/geometry.d.ts +9 -0
  68. package/dist/utils/geometry.d.ts.map +1 -0
  69. package/dist/utils/geometry.js +31 -0
  70. package/dist/utils/geometry.js.map +1 -0
  71. package/package.json +59 -4
  72. package/src/components/BoundingBox.tsx +63 -0
  73. package/src/components/DropzonePreview.tsx +61 -0
  74. package/src/components/DropzonePrompt.tsx +46 -0
  75. package/src/components/OcrDropzone.tsx +117 -0
  76. package/src/components/OcrOverlay.tsx +100 -0
  77. package/src/context/RustoContext.ts +4 -0
  78. package/src/context/RustoProvider.tsx +62 -0
  79. package/src/hooks/useOcr.ts +73 -0
  80. package/src/hooks/useRusto.ts +11 -0
  81. package/src/index.ts +34 -0
  82. package/src/services/ocrService.ts +43 -0
  83. package/src/types/components.ts +48 -0
  84. package/src/types/context.ts +18 -0
  85. package/src/types/hooks.ts +22 -0
  86. package/src/types/index.ts +3 -0
  87. package/src/utils/formatters.ts +27 -0
  88. package/src/utils/geometry.ts +36 -0
package/dist/index.js ADDED
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.resultsToJson = exports.resultsToCsv = exports.resultsToText = exports.calculateScale = exports.scaleQuad = exports.scaleFrame = exports.DropzonePreview = exports.DropzonePrompt = exports.OcrDropzone = exports.BoundingBox = exports.OcrOverlay = exports.defaultOcrService = exports.OcrService = exports.useOcr = exports.useRusto = exports.RustoContext = exports.RustoProvider = void 0;
18
+ // Provider & Context
19
+ var RustoProvider_js_1 = require("./context/RustoProvider.js");
20
+ Object.defineProperty(exports, "RustoProvider", { enumerable: true, get: function () { return RustoProvider_js_1.RustoProvider; } });
21
+ var RustoContext_js_1 = require("./context/RustoContext.js");
22
+ Object.defineProperty(exports, "RustoContext", { enumerable: true, get: function () { return RustoContext_js_1.RustoContext; } });
23
+ // Hooks
24
+ var useRusto_js_1 = require("./hooks/useRusto.js");
25
+ Object.defineProperty(exports, "useRusto", { enumerable: true, get: function () { return useRusto_js_1.useRusto; } });
26
+ var useOcr_js_1 = require("./hooks/useOcr.js");
27
+ Object.defineProperty(exports, "useOcr", { enumerable: true, get: function () { return useOcr_js_1.useOcr; } });
28
+ // Services
29
+ var ocrService_js_1 = require("./services/ocrService.js");
30
+ Object.defineProperty(exports, "OcrService", { enumerable: true, get: function () { return ocrService_js_1.OcrService; } });
31
+ Object.defineProperty(exports, "defaultOcrService", { enumerable: true, get: function () { return ocrService_js_1.defaultOcrService; } });
32
+ // UI Components
33
+ var OcrOverlay_js_1 = require("./components/OcrOverlay.js");
34
+ Object.defineProperty(exports, "OcrOverlay", { enumerable: true, get: function () { return OcrOverlay_js_1.OcrOverlay; } });
35
+ var BoundingBox_js_1 = require("./components/BoundingBox.js");
36
+ Object.defineProperty(exports, "BoundingBox", { enumerable: true, get: function () { return BoundingBox_js_1.BoundingBox; } });
37
+ var OcrDropzone_js_1 = require("./components/OcrDropzone.js");
38
+ Object.defineProperty(exports, "OcrDropzone", { enumerable: true, get: function () { return OcrDropzone_js_1.OcrDropzone; } });
39
+ var DropzonePrompt_js_1 = require("./components/DropzonePrompt.js");
40
+ Object.defineProperty(exports, "DropzonePrompt", { enumerable: true, get: function () { return DropzonePrompt_js_1.DropzonePrompt; } });
41
+ var DropzonePreview_js_1 = require("./components/DropzonePreview.js");
42
+ Object.defineProperty(exports, "DropzonePreview", { enumerable: true, get: function () { return DropzonePreview_js_1.DropzonePreview; } });
43
+ // Geometry & Formatters
44
+ var geometry_js_1 = require("./utils/geometry.js");
45
+ Object.defineProperty(exports, "scaleFrame", { enumerable: true, get: function () { return geometry_js_1.scaleFrame; } });
46
+ Object.defineProperty(exports, "scaleQuad", { enumerable: true, get: function () { return geometry_js_1.scaleQuad; } });
47
+ Object.defineProperty(exports, "calculateScale", { enumerable: true, get: function () { return geometry_js_1.calculateScale; } });
48
+ var formatters_js_1 = require("./utils/formatters.js");
49
+ Object.defineProperty(exports, "resultsToText", { enumerable: true, get: function () { return formatters_js_1.resultsToText; } });
50
+ Object.defineProperty(exports, "resultsToCsv", { enumerable: true, get: function () { return formatters_js_1.resultsToCsv; } });
51
+ Object.defineProperty(exports, "resultsToJson", { enumerable: true, get: function () { return formatters_js_1.resultsToJson; } });
52
+ // Types
53
+ __exportStar(require("./types/index.js"), exports);
54
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,qBAAqB;AACrB,+DAA2D;AAAlD,iHAAA,aAAa,OAAA;AACtB,6DAAyD;AAAhD,+GAAA,YAAY,OAAA;AAErB,QAAQ;AACR,mDAA+C;AAAtC,uGAAA,QAAQ,OAAA;AACjB,+CAA2C;AAAlC,mGAAA,MAAM,OAAA;AAEf,WAAW;AACX,0DAAyE;AAAhE,2GAAA,UAAU,OAAA;AAAE,kHAAA,iBAAiB,OAAA;AAEtC,gBAAgB;AAChB,4DAAwD;AAA/C,2GAAA,UAAU,OAAA;AACnB,8DAA0D;AAAjD,6GAAA,WAAW,OAAA;AACpB,8DAA0D;AAAjD,6GAAA,WAAW,OAAA;AACpB,oEAAgE;AAAvD,mHAAA,cAAc,OAAA;AACvB,sEAAkE;AAAzD,qHAAA,eAAe,OAAA;AAExB,wBAAwB;AACxB,mDAA4E;AAAnE,yGAAA,UAAU,OAAA;AAAE,wGAAA,SAAS,OAAA;AAAE,6GAAA,cAAc,OAAA;AAC9C,uDAAmF;AAA1E,8GAAA,aAAa,OAAA;AAAE,6GAAA,YAAY,OAAA;AAAE,8GAAA,aAAa,OAAA;AAEnD,QAAQ;AACR,mDAAiC"}
@@ -0,0 +1,12 @@
1
+ import { WebOcrEngine, type DetectTextOptions, type ImageSource, type InitializeConfig, type TextResult } from '@rustors/web';
2
+ export declare class OcrService {
3
+ private engine;
4
+ constructor(engineInstance?: WebOcrEngine);
5
+ init(config?: InitializeConfig): Promise<void>;
6
+ isReady(): boolean;
7
+ detect(source: ImageSource, options?: DetectTextOptions): Promise<TextResult[] | string>;
8
+ terminate(): void;
9
+ getEngine(): WebOcrEngine;
10
+ }
11
+ export declare const defaultOcrService: OcrService;
12
+ //# sourceMappingURL=ocrService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ocrService.d.ts","sourceRoot":"","sources":["../../src/services/ocrService.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;AAEtB,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAe;gBAEjB,cAAc,CAAC,EAAE,YAAY;IAI5B,IAAI,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpD,OAAO,IAAI,OAAO;IAIZ,MAAM,CACjB,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC;IAO1B,SAAS,IAAI,IAAI;IAIjB,SAAS,IAAI,YAAY;CAGjC;AAED,eAAO,MAAM,iBAAiB,YAAmB,CAAC"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultOcrService = exports.OcrService = void 0;
4
+ const web_1 = require("@rustors/web");
5
+ class OcrService {
6
+ constructor(engineInstance) {
7
+ this.engine = engineInstance || new web_1.WebOcrEngine();
8
+ }
9
+ async init(config) {
10
+ await this.engine.initialize(config);
11
+ }
12
+ isReady() {
13
+ return this.engine.isInitialized();
14
+ }
15
+ async detect(source, options) {
16
+ if (!this.engine.isInitialized()) {
17
+ throw new Error('OCR Service engine is not ready. Call init() first.');
18
+ }
19
+ return this.engine.detectText(source, options);
20
+ }
21
+ terminate() {
22
+ this.engine.terminate();
23
+ }
24
+ getEngine() {
25
+ return this.engine;
26
+ }
27
+ }
28
+ exports.OcrService = OcrService;
29
+ exports.defaultOcrService = new OcrService();
30
+ //# sourceMappingURL=ocrService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ocrService.js","sourceRoot":"","sources":["../../src/services/ocrService.ts"],"names":[],"mappings":";;;AAAA,sCAMsB;AAEtB,MAAa,UAAU;IAGrB,YAAY,cAA6B;QACvC,IAAI,CAAC,MAAM,GAAG,cAAc,IAAI,IAAI,kBAAY,EAAE,CAAC;IACrD,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,MAAyB;QACzC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IAEM,OAAO;QACZ,OAAO,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;IACrC,CAAC;IAEM,KAAK,CAAC,MAAM,CACjB,MAAmB,EACnB,OAA2B;QAE3B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IAEM,SAAS;QACd,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;IAC1B,CAAC;IAEM,SAAS;QACd,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;CACF;AAhCD,gCAgCC;AAEY,QAAA,iBAAiB,GAAG,IAAI,UAAU,EAAE,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { CSSProperties, ReactNode } from 'react';
2
+ import type { TextResult, DetectTextOptions } from '@rustors/web';
3
+ export interface BoundingBoxProps {
4
+ item: TextResult;
5
+ scaleX: number;
6
+ scaleY: number;
7
+ highlightColor?: string;
8
+ showTooltip?: boolean;
9
+ className?: string;
10
+ style?: CSSProperties;
11
+ onClick?: (item: TextResult) => void;
12
+ }
13
+ export interface OcrOverlayProps {
14
+ imageSrc: string;
15
+ results: TextResult[];
16
+ naturalWidth?: number;
17
+ naturalHeight?: number;
18
+ highlightColor?: string;
19
+ showTooltips?: boolean;
20
+ className?: string;
21
+ style?: CSSProperties;
22
+ onItemClick?: (item: TextResult) => void;
23
+ }
24
+ export interface DropzonePromptProps {
25
+ title?: string;
26
+ subtitle?: string;
27
+ icon?: ReactNode;
28
+ className?: string;
29
+ }
30
+ export interface DropzonePreviewProps {
31
+ previewUrl: string;
32
+ onClear: () => void;
33
+ className?: string;
34
+ }
35
+ export interface OcrDropzoneProps {
36
+ onFileSelect?: (file: File) => void;
37
+ onDetect?: (results: TextResult[] | string) => void;
38
+ autoDetect?: boolean;
39
+ detectOptions?: DetectTextOptions;
40
+ accept?: string;
41
+ className?: string;
42
+ style?: CSSProperties;
43
+ }
44
+ //# sourceMappingURL=components.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/types/components.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAElE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,KAAK,IAAI,CAAC;IACpD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=components.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.js","sourceRoot":"","sources":["../../src/types/components.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ import type { InitializeConfig } from '@rustors/web';
2
+ import type { OcrService } from '../services/ocrService.js';
3
+ export type RustoStatus = 'idle' | 'initializing' | 'ready' | 'error';
4
+ export interface RustoContextValue {
5
+ status: RustoStatus;
6
+ isReady: boolean;
7
+ error: Error | null;
8
+ initialize: (config?: InitializeConfig) => Promise<void>;
9
+ service: OcrService;
10
+ }
11
+ export interface RustoProviderProps {
12
+ children: React.ReactNode;
13
+ config?: InitializeConfig;
14
+ autoInitialize?: boolean;
15
+ }
16
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/types/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,cAAc,GAAG,OAAO,GAAG,OAAO,CAAC;AAEtE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/types/context.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ import type { DetectTextOptions, ImageSource, TextResult } from '@rustors/web';
2
+ export interface UseOcrOptions extends DetectTextOptions {
3
+ onSuccess?: (results: TextResult[] | string) => void;
4
+ onError?: (error: Error) => void;
5
+ }
6
+ export interface UseOcrReturn {
7
+ detect: (source: ImageSource, overrideOptions?: DetectTextOptions) => Promise<TextResult[] | string>;
8
+ results: TextResult[];
9
+ spatialText: string;
10
+ isProcessing: boolean;
11
+ error: Error | null;
12
+ reset: () => void;
13
+ }
14
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/types/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACX,UAAU,EACX,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,KAAK,IAAI,CAAC;IACrD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,CACN,MAAM,EAAE,WAAW,EACnB,eAAe,CAAC,EAAE,iBAAiB,KAChC,OAAO,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,CAAC;IACpC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/types/hooks.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ export * from './context.js';
2
+ export * from './hooks.js';
3
+ export * from './components.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./context.js"), exports);
18
+ __exportStar(require("./hooks.js"), exports);
19
+ __exportStar(require("./components.js"), exports);
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA6B;AAC7B,6CAA2B;AAC3B,kDAAgC"}
@@ -0,0 +1,14 @@
1
+ import type { TextResult } from '@rustors/web';
2
+ /**
3
+ * Combines all recognized text tokens into a single text block.
4
+ */
5
+ export declare function resultsToText(results: TextResult[]): string;
6
+ /**
7
+ * Formats OCR results into standard CSV with bounding box coordinates and scores.
8
+ */
9
+ export declare function resultsToCsv(results: TextResult[]): string;
10
+ /**
11
+ * Pretty formats OCR results as a JSON string.
12
+ */
13
+ export declare function resultsToJson(results: TextResult[], indent?: number): string;
14
+ //# sourceMappingURL=formatters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../../src/utils/formatters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAE3D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAO1D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,MAAM,GAAE,MAAU,GAAG,MAAM,CAE/E"}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resultsToText = resultsToText;
4
+ exports.resultsToCsv = resultsToCsv;
5
+ exports.resultsToJson = resultsToJson;
6
+ /**
7
+ * Combines all recognized text tokens into a single text block.
8
+ */
9
+ function resultsToText(results) {
10
+ return results.map((r) => r.text).join('\n');
11
+ }
12
+ /**
13
+ * Formats OCR results into standard CSV with bounding box coordinates and scores.
14
+ */
15
+ function resultsToCsv(results) {
16
+ const header = 'text,score,left,top,width,height';
17
+ const rows = results.map((r) => {
18
+ const escapedText = `"${r.text.replace(/"/g, '""')}"`;
19
+ return `${escapedText},${r.score.toFixed(4)},${r.frame.left},${r.frame.top},${r.frame.width},${r.frame.height}`;
20
+ });
21
+ return [header, ...rows].join('\n');
22
+ }
23
+ /**
24
+ * Pretty formats OCR results as a JSON string.
25
+ */
26
+ function resultsToJson(results, indent = 2) {
27
+ return JSON.stringify(results, null, indent);
28
+ }
29
+ //# sourceMappingURL=formatters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters.js","sourceRoot":"","sources":["../../src/utils/formatters.ts"],"names":[],"mappings":";;AAKA,sCAEC;AAKD,oCAOC;AAKD,sCAEC;AAxBD;;GAEG;AACH,SAAgB,aAAa,CAAC,OAAqB;IACjD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,OAAqB;IAChD,MAAM,MAAM,GAAG,kCAAkC,CAAC;IAClD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC7B,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;QACtD,OAAO,GAAG,WAAW,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;IAClH,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,SAAgB,aAAa,CAAC,OAAqB,EAAE,SAAiB,CAAC;IACrE,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { Frame } from '@rustors/web';
2
+ export type QuadPoints = [[number, number], [number, number], [number, number], [number, number]];
3
+ export declare function scaleFrame(frame: Frame, scaleX: number, scaleY: number): Frame;
4
+ export declare function scaleQuad(quad: QuadPoints, scaleX: number, scaleY: number): QuadPoints;
5
+ export declare function calculateScale(naturalWidth: number, naturalHeight: number, renderWidth: number, renderHeight: number): {
6
+ scaleX: number;
7
+ scaleY: number;
8
+ };
9
+ //# sourceMappingURL=geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../../src/utils/geometry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAElG,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAO9E;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAOtF;AAED,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAQpC"}
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scaleFrame = scaleFrame;
4
+ exports.scaleQuad = scaleQuad;
5
+ exports.calculateScale = calculateScale;
6
+ function scaleFrame(frame, scaleX, scaleY) {
7
+ return {
8
+ left: Math.round(frame.left * scaleX * 100) / 100,
9
+ top: Math.round(frame.top * scaleY * 100) / 100,
10
+ width: Math.round(frame.width * scaleX * 100) / 100,
11
+ height: Math.round(frame.height * scaleY * 100) / 100,
12
+ };
13
+ }
14
+ function scaleQuad(quad, scaleX, scaleY) {
15
+ return [
16
+ [Math.round(quad[0][0] * scaleX * 100) / 100, Math.round(quad[0][1] * scaleY * 100) / 100],
17
+ [Math.round(quad[1][0] * scaleX * 100) / 100, Math.round(quad[1][1] * scaleY * 100) / 100],
18
+ [Math.round(quad[2][0] * scaleX * 100) / 100, Math.round(quad[2][1] * scaleY * 100) / 100],
19
+ [Math.round(quad[3][0] * scaleX * 100) / 100, Math.round(quad[3][1] * scaleY * 100) / 100],
20
+ ];
21
+ }
22
+ function calculateScale(naturalWidth, naturalHeight, renderWidth, renderHeight) {
23
+ if (naturalWidth <= 0 || naturalHeight <= 0 || renderWidth <= 0 || renderHeight <= 0) {
24
+ return { scaleX: 1, scaleY: 1 };
25
+ }
26
+ return {
27
+ scaleX: renderWidth / naturalWidth,
28
+ scaleY: renderHeight / naturalHeight,
29
+ };
30
+ }
31
+ //# sourceMappingURL=geometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.js","sourceRoot":"","sources":["../../src/utils/geometry.ts"],"names":[],"mappings":";;AAIA,gCAOC;AAED,8BAOC;AAED,wCAaC;AA/BD,SAAgB,UAAU,CAAC,KAAY,EAAE,MAAc,EAAE,MAAc;IACrE,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG;QACjD,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG;QAC/C,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG;QACnD,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG;KACtD,CAAC;AACJ,CAAC;AAED,SAAgB,SAAS,CAAC,IAAgB,EAAE,MAAc,EAAE,MAAc;IACxE,OAAO;QACL,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;QAC1F,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;QAC1F,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;QAC1F,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;KAC3F,CAAC;AACJ,CAAC;AAED,SAAgB,cAAc,CAC5B,YAAoB,EACpB,aAAqB,EACrB,WAAmB,EACnB,YAAoB;IAEpB,IAAI,YAAY,IAAI,CAAC,IAAI,aAAa,IAAI,CAAC,IAAI,WAAW,IAAI,CAAC,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;QACrF,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAClC,CAAC;IACD,OAAO;QACL,MAAM,EAAE,WAAW,GAAG,YAAY;QAClC,MAAM,EAAE,YAAY,GAAG,aAAa;KACrC,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@rustors/react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "description": "React hooks, provider, and UI components for RustO! OCR engine",
5
+ "keywords": [
6
+ "ocr",
7
+ "rusto",
8
+ "react",
9
+ "hooks",
10
+ "components",
11
+ "text-recognition",
12
+ "typescript"
13
+ ],
14
+ "homepage": "https://github.com/byrizki/rusto-rs#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/byrizki/rusto-rs/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "byrizki <contact@byrizki.com> (https://github.com/byrizki)",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/byrizki/rusto-rs.git",
23
+ "directory": "packages/react"
24
+ },
25
+ "main": "dist/index.js",
26
+ "module": "dist/index.mjs",
27
+ "types": "dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.mjs",
32
+ "require": "./dist/index.js"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "src",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public",
44
+ "registry": "https://registry.npmjs.org/"
45
+ },
46
+ "scripts": {
47
+ "build": "tsc -p tsconfig.build.json",
48
+ "typecheck": "tsc --noEmit",
49
+ "clean": "rm -rf dist"
50
+ },
51
+ "peerDependencies": {
52
+ "react": ">=18.0.0",
53
+ "@rustors/web": ">=0.2.5"
54
+ },
55
+ "devDependencies": {
56
+ "@types/react": "^18.2.0",
57
+ "react": "^18.2.0",
58
+ "@rustors/web": "file:../web",
59
+ "typescript": "^5.0.2"
60
+ }
61
+ }
@@ -0,0 +1,63 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { BoundingBoxProps } from '../types/index.js';
3
+ import { scaleFrame } from '../utils/geometry.js';
4
+
5
+ export function BoundingBox({
6
+ item,
7
+ scaleX,
8
+ scaleY,
9
+ highlightColor = 'rgba(59, 130, 246, 0.4)',
10
+ showTooltip = true,
11
+ className,
12
+ style,
13
+ onClick,
14
+ }: BoundingBoxProps): ReactElement {
15
+ const scaled = scaleFrame(item.frame, scaleX, scaleY);
16
+
17
+ const boxStyle: React.CSSProperties = {
18
+ position: 'absolute',
19
+ left: `${scaled.left}px`,
20
+ top: `${scaled.top}px`,
21
+ width: `${scaled.width}px`,
22
+ height: `${scaled.height}px`,
23
+ border: `1.5px solid ${highlightColor}`,
24
+ backgroundColor: highlightColor.replace(/[\d.]+\)$/, '0.15)'),
25
+ cursor: onClick ? 'pointer' : 'default',
26
+ pointerEvents: 'auto',
27
+ boxSizing: 'border-box',
28
+ borderRadius: '2px',
29
+ ...style,
30
+ };
31
+
32
+ const tooltipStyle: React.CSSProperties = {
33
+ position: 'absolute',
34
+ bottom: '100%',
35
+ left: '0',
36
+ backgroundColor: '#1f2937',
37
+ color: '#ffffff',
38
+ fontSize: '11px',
39
+ lineHeight: '1.2',
40
+ padding: '2px 6px',
41
+ borderRadius: '4px',
42
+ whiteSpace: 'nowrap',
43
+ pointerEvents: 'none',
44
+ zIndex: 10,
45
+ transform: 'translateY(-2px)',
46
+ boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
47
+ };
48
+
49
+ return (
50
+ <div
51
+ className={className}
52
+ style={boxStyle}
53
+ onClick={() => onClick?.(item)}
54
+ title={`${item.text} (${Math.round(item.score * 100)}%)`}
55
+ >
56
+ {showTooltip && (
57
+ <span style={tooltipStyle}>
58
+ {item.text} <small style={{ opacity: 0.75 }}>({Math.round(item.score * 100)}%)</small>
59
+ </span>
60
+ )}
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,61 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { DropzonePreviewProps } from '../types/index.js';
3
+
4
+ export function DropzonePreview({
5
+ previewUrl,
6
+ onClear,
7
+ className,
8
+ }: DropzonePreviewProps): ReactElement {
9
+ return (
10
+ <div
11
+ className={className}
12
+ style={{
13
+ position: 'relative',
14
+ display: 'flex',
15
+ alignItems: 'center',
16
+ justifyContent: 'center',
17
+ padding: '12px',
18
+ width: '100%',
19
+ boxSizing: 'border-box',
20
+ }}
21
+ >
22
+ <img
23
+ src={previewUrl}
24
+ alt="Uploaded Preview"
25
+ style={{
26
+ maxHeight: '260px',
27
+ maxWidth: '100%',
28
+ objectFit: 'contain',
29
+ borderRadius: '4px',
30
+ }}
31
+ />
32
+ <button
33
+ type="button"
34
+ onClick={(e) => {
35
+ e.stopPropagation();
36
+ onClear();
37
+ }}
38
+ style={{
39
+ position: 'absolute',
40
+ top: '16px',
41
+ right: '16px',
42
+ backgroundColor: 'rgba(0, 0, 0, 0.65)',
43
+ color: '#ffffff',
44
+ border: 'none',
45
+ borderRadius: '50%',
46
+ width: '28px',
47
+ height: '28px',
48
+ display: 'flex',
49
+ alignItems: 'center',
50
+ justifyContent: 'center',
51
+ cursor: 'pointer',
52
+ fontSize: '14px',
53
+ lineHeight: 1,
54
+ }}
55
+ aria-label="Remove image"
56
+ >
57
+ ×
58
+ </button>
59
+ </div>
60
+ );
61
+ }
@@ -0,0 +1,46 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { DropzonePromptProps } from '../types/index.js';
3
+
4
+ export function DropzonePrompt({
5
+ title = 'Click or drag image here to scan',
6
+ subtitle = 'Supports PNG, JPEG, WebP, and document scans',
7
+ icon,
8
+ className,
9
+ }: DropzonePromptProps): ReactElement {
10
+ return (
11
+ <div
12
+ className={className}
13
+ style={{
14
+ display: 'flex',
15
+ flexDirection: 'column',
16
+ alignItems: 'center',
17
+ justifyContent: 'center',
18
+ padding: '32px 16px',
19
+ textAlign: 'center',
20
+ pointerEvents: 'none',
21
+ }}
22
+ >
23
+ {icon || (
24
+ <svg
25
+ style={{ width: '40px', height: '40px', color: '#6b7280', marginBottom: '12px' }}
26
+ fill="none"
27
+ stroke="currentColor"
28
+ viewBox="0 0 24 24"
29
+ >
30
+ <path
31
+ strokeLinecap="round"
32
+ strokeLinejoin="round"
33
+ strokeWidth={1.5}
34
+ d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
35
+ />
36
+ </svg>
37
+ )}
38
+ <p style={{ margin: '0 0 4px', fontSize: '14px', fontWeight: 600, color: '#374151' }}>
39
+ {title}
40
+ </p>
41
+ <p style={{ margin: 0, fontSize: '12px', color: '#6b7280' }}>
42
+ {subtitle}
43
+ </p>
44
+ </div>
45
+ );
46
+ }