@emblemvault/hustle-react 1.2.0 → 1.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.
package/README.md CHANGED
@@ -111,6 +111,62 @@ Complete chat UI component.
111
111
  />
112
112
  ```
113
113
 
114
+ ### HustleChatWidget
115
+
116
+ Floating chat widget for site-wide chatbot integration. Perfect for customer support or AI assistants that persist across page navigations.
117
+
118
+ ```tsx
119
+ import { HustleProvider, HustleChatWidget } from '@emblemvault/hustle-react';
120
+
121
+ // Add to your Next.js layout for site-wide availability
122
+ export default function RootLayout({ children }) {
123
+ return (
124
+ <html>
125
+ <body>
126
+ <HustleProvider>
127
+ {children}
128
+ <HustleChatWidget
129
+ config={{
130
+ position: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
131
+ size: 'md', // 'sm' | 'md' | 'lg' | 'xl' | 'full'
132
+ title: 'Support', // Header title
133
+ defaultOpen: false, // Start open?
134
+ offset: { x: 24, y: 24 }, // Edge offset in pixels
135
+ storageKey: 'chat-open', // localStorage key for persistence (false to disable)
136
+ showBadge: true, // Show notification badge
137
+ badgeContent: 3, // Badge content
138
+ onOpen: () => {}, // Called when widget opens
139
+ onClose: () => {}, // Called when widget closes
140
+ }}
141
+ placeholder="How can we help?"
142
+ showSettings
143
+ />
144
+ </HustleProvider>
145
+ </body>
146
+ </html>
147
+ );
148
+ }
149
+ ```
150
+
151
+ #### Widget Configuration
152
+
153
+ | Option | Type | Default | Description |
154
+ |--------|------|---------|-------------|
155
+ | `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'` | Screen position |
156
+ | `size` | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | Chat panel size |
157
+ | `title` | `string` | `'Chat'` | Panel header title |
158
+ | `defaultOpen` | `boolean` | `false` | Whether widget starts open |
159
+ | `offset` | `{ x: number, y: number }` | `{ x: 24, y: 24 }` | Offset from screen edge |
160
+ | `storageKey` | `string \| false` | `'hustle-widget-open'` | Key for persisting state |
161
+ | `showBadge` | `boolean` | `false` | Show notification badge |
162
+ | `badgeContent` | `string \| number` | - | Badge content |
163
+ | `launcherIcon` | `React.ReactNode` | Chat bubble | Custom launcher icon |
164
+ | `launcherStyle` | `React.CSSProperties` | - | Custom launcher styles |
165
+ | `panelStyle` | `React.CSSProperties` | - | Custom panel styles |
166
+ | `zIndex` | `number` | `9999` | Widget z-index |
167
+ | `onOpen` | `() => void` | - | Called when widget opens |
168
+ | `onClose` | `() => void` | - | Called when widget closes |
169
+
114
170
  ## Plugins
115
171
 
116
172
  Extend the AI with custom tools.
@@ -4605,6 +4605,14 @@ var animations = `
4605
4605
  text-shadow: 0 0 8px ${defaults.colors.accentPrimary};
4606
4606
  }
4607
4607
  }
4608
+ /* Hide scrollbar while maintaining scroll functionality */
4609
+ .hustle-hide-scrollbar {
4610
+ -ms-overflow-style: none; /* IE and Edge */
4611
+ scrollbar-width: none; /* Firefox */
4612
+ }
4613
+ .hustle-hide-scrollbar::-webkit-scrollbar {
4614
+ display: none; /* Chrome, Safari, Opera */
4615
+ }
4608
4616
  `;
4609
4617
 
4610
4618
  // ../../node_modules/marked/lib/marked.esm.js
@@ -15071,6 +15079,7 @@ function HustleChat({
15071
15079
  placeholder = "Type a message...",
15072
15080
  showSettings = false,
15073
15081
  showDebug = false,
15082
+ hideHeader = false,
15074
15083
  initialSystemPrompt = "",
15075
15084
  onMessage,
15076
15085
  onToolCall,
@@ -15234,7 +15243,7 @@ function HustleChat({
15234
15243
  return /* @__PURE__ */ jsxs(Fragment, { children: [
15235
15244
  /* @__PURE__ */ jsx("style", { children: animations }),
15236
15245
  /* @__PURE__ */ jsxs("div", { className, style: styles.container, children: [
15237
- /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
15246
+ !hideHeader && /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
15238
15247
  /* @__PURE__ */ jsx("h2", { style: styles.headerTitle, children: "Chat" }),
15239
15248
  /* @__PURE__ */ jsxs("div", { style: styles.headerActions, children: [
15240
15249
  selectedModel && /* @__PURE__ */ jsx("span", { style: { fontSize: tokens.typography.fontSizeSm, color: tokens.colors.textSecondary }, children: selectedModel.split("/").pop() }),
@@ -15409,7 +15418,7 @@ function HustleChat({
15409
15418
  ] })
15410
15419
  ] })
15411
15420
  ] }) }),
15412
- /* @__PURE__ */ jsxs("div", { style: styles.messagesArea, children: [
15421
+ /* @__PURE__ */ jsxs("div", { className: "hustle-hide-scrollbar", style: styles.messagesArea, children: [
15413
15422
  messages.length === 0 && /* @__PURE__ */ jsx("div", { style: styles.messagesEmpty, children: /* @__PURE__ */ jsx("p", { children: getPlaceholderMessage() }) }),
15414
15423
  /* @__PURE__ */ jsx("div", { style: styles.messagesContainer, children: messages.map((message) => /* @__PURE__ */ jsx(
15415
15424
  MessageBubble,
@@ -15536,6 +15545,343 @@ function SettingsIcon() {
15536
15545
  function AttachIcon() {
15537
15546
  return /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
15538
15547
  }
15548
+ var sizeConfigs = {
15549
+ sm: { width: "320px", height: "400px" },
15550
+ md: { width: "380px", height: "520px" },
15551
+ lg: { width: "420px", height: "600px" },
15552
+ xl: { width: "480px", height: "700px" },
15553
+ full: { width: "100vw", height: "100vh" }
15554
+ };
15555
+ var widgetStyles = {
15556
+ // Container for absolute positioning
15557
+ container: {
15558
+ position: "fixed",
15559
+ zIndex: 9999,
15560
+ fontFamily: tokens.typography.fontFamily
15561
+ },
15562
+ // Launcher button
15563
+ launcher: {
15564
+ width: "56px",
15565
+ height: "56px",
15566
+ borderRadius: tokens.radius.full,
15567
+ background: `linear-gradient(135deg, ${tokens.colors.accentPrimary} 0%, #2d7dd2 100%)`,
15568
+ border: "none",
15569
+ cursor: "pointer",
15570
+ display: "flex",
15571
+ alignItems: "center",
15572
+ justifyContent: "center",
15573
+ boxShadow: `0 4px 20px rgba(76, 154, 255, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3)`,
15574
+ transition: `all ${tokens.transitions.normal}`,
15575
+ color: tokens.colors.textInverse
15576
+ },
15577
+ launcherHover: {
15578
+ transform: "scale(1.05)",
15579
+ boxShadow: `0 6px 24px rgba(76, 154, 255, 0.5), 0 4px 12px rgba(0, 0, 0, 0.4)`
15580
+ },
15581
+ // Badge
15582
+ badge: {
15583
+ position: "absolute",
15584
+ top: "-4px",
15585
+ right: "-4px",
15586
+ minWidth: "20px",
15587
+ height: "20px",
15588
+ borderRadius: tokens.radius.pill,
15589
+ background: tokens.colors.accentError,
15590
+ color: tokens.colors.textInverse,
15591
+ fontSize: tokens.typography.fontSizeXs,
15592
+ fontWeight: tokens.typography.fontWeightSemibold,
15593
+ display: "flex",
15594
+ alignItems: "center",
15595
+ justifyContent: "center",
15596
+ padding: "0 6px",
15597
+ border: `2px solid ${tokens.colors.bgPrimary}`
15598
+ },
15599
+ // Chat panel
15600
+ panel: {
15601
+ position: "absolute",
15602
+ borderRadius: tokens.radius.xl,
15603
+ background: tokens.colors.bgSecondary,
15604
+ border: `1px solid ${tokens.colors.borderPrimary}`,
15605
+ boxShadow: `0 16px 48px rgba(0, 0, 0, 0.5)`,
15606
+ overflow: "hidden",
15607
+ display: "flex",
15608
+ flexDirection: "column",
15609
+ transition: `all ${tokens.transitions.slow}`
15610
+ },
15611
+ panelHidden: {
15612
+ opacity: 0,
15613
+ transform: "scale(0.95) translateY(10px)",
15614
+ pointerEvents: "none"
15615
+ },
15616
+ panelVisible: {
15617
+ opacity: 1,
15618
+ transform: "scale(1) translateY(0)"
15619
+ },
15620
+ // Full screen mode overrides
15621
+ panelFull: {
15622
+ position: "fixed",
15623
+ top: 0,
15624
+ left: 0,
15625
+ right: 0,
15626
+ bottom: 0,
15627
+ borderRadius: 0,
15628
+ width: "100vw",
15629
+ height: "100vh"
15630
+ },
15631
+ // Close button in panel header
15632
+ closeBtn: {
15633
+ width: "32px",
15634
+ height: "32px",
15635
+ borderRadius: tokens.radius.md,
15636
+ background: "transparent",
15637
+ border: "none",
15638
+ color: tokens.colors.textTertiary,
15639
+ cursor: "pointer",
15640
+ display: "flex",
15641
+ alignItems: "center",
15642
+ justifyContent: "center",
15643
+ transition: `all ${tokens.transitions.fast}`
15644
+ },
15645
+ closeBtnHover: {
15646
+ background: tokens.colors.bgTertiary,
15647
+ color: tokens.colors.textPrimary
15648
+ }
15649
+ };
15650
+ function ChatIcon() {
15651
+ return /* @__PURE__ */ jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
15652
+ }
15653
+ function CloseIcon() {
15654
+ return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
15655
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
15656
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
15657
+ ] });
15658
+ }
15659
+ function MinimizeIcon() {
15660
+ return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
15661
+ /* @__PURE__ */ jsx("polyline", { points: "4 14 10 14 10 20" }),
15662
+ /* @__PURE__ */ jsx("polyline", { points: "20 10 14 10 14 4" }),
15663
+ /* @__PURE__ */ jsx("line", { x1: "14", y1: "10", x2: "21", y2: "3" }),
15664
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
15665
+ ] });
15666
+ }
15667
+ function HustleChatWidget({
15668
+ config = {},
15669
+ ...chatProps
15670
+ }) {
15671
+ const {
15672
+ position = "bottom-right",
15673
+ size = "md",
15674
+ title = "Chat",
15675
+ defaultOpen = false,
15676
+ launcherIcon,
15677
+ offset = { x: 24, y: 24 },
15678
+ zIndex = 9999,
15679
+ showBadge = false,
15680
+ badgeContent,
15681
+ launcherStyle,
15682
+ panelStyle,
15683
+ onOpen,
15684
+ onClose,
15685
+ storageKey = "hustle-widget-open"
15686
+ } = config;
15687
+ const [isOpen, setIsOpen] = useState(false);
15688
+ const [isHovered, setIsHovered] = useState(false);
15689
+ const [closeHovered, setCloseHovered] = useState(false);
15690
+ const [mounted, setMounted] = useState(false);
15691
+ useEffect(() => {
15692
+ setMounted(true);
15693
+ if (storageKey && typeof window !== "undefined") {
15694
+ const stored = localStorage.getItem(storageKey);
15695
+ if (stored !== null) {
15696
+ setIsOpen(stored === "true");
15697
+ } else {
15698
+ setIsOpen(defaultOpen);
15699
+ }
15700
+ } else {
15701
+ setIsOpen(defaultOpen);
15702
+ }
15703
+ }, [defaultOpen, storageKey]);
15704
+ useEffect(() => {
15705
+ if (!mounted) return;
15706
+ if (storageKey && typeof window !== "undefined") {
15707
+ localStorage.setItem(storageKey, String(isOpen));
15708
+ }
15709
+ }, [isOpen, storageKey, mounted]);
15710
+ const toggle = useCallback(() => {
15711
+ setIsOpen((prev) => {
15712
+ const next = !prev;
15713
+ if (next) {
15714
+ onOpen?.();
15715
+ } else {
15716
+ onClose?.();
15717
+ }
15718
+ return next;
15719
+ });
15720
+ }, [onOpen, onClose]);
15721
+ const positionStyles = getPositionStyles(position, offset);
15722
+ const sizeConfig = sizeConfigs[size];
15723
+ if (!mounted) {
15724
+ return null;
15725
+ }
15726
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
15727
+ /* @__PURE__ */ jsx("style", { children: animations }),
15728
+ /* @__PURE__ */ jsx("style", { children: `
15729
+ @keyframes hustle-widget-bounce {
15730
+ 0%, 100% { transform: translateY(0); }
15731
+ 50% { transform: translateY(-4px); }
15732
+ }
15733
+ ` }),
15734
+ /* @__PURE__ */ jsxs(
15735
+ "div",
15736
+ {
15737
+ style: {
15738
+ ...widgetStyles.container,
15739
+ ...positionStyles.container,
15740
+ zIndex
15741
+ },
15742
+ children: [
15743
+ /* @__PURE__ */ jsxs(
15744
+ "div",
15745
+ {
15746
+ style: {
15747
+ ...widgetStyles.panel,
15748
+ ...positionStyles.panel,
15749
+ ...size === "full" ? widgetStyles.panelFull : {
15750
+ width: sizeConfig.width,
15751
+ height: sizeConfig.height
15752
+ },
15753
+ ...isOpen ? widgetStyles.panelVisible : widgetStyles.panelHidden,
15754
+ ...panelStyle
15755
+ },
15756
+ children: [
15757
+ /* @__PURE__ */ jsxs(
15758
+ "div",
15759
+ {
15760
+ style: {
15761
+ display: "flex",
15762
+ alignItems: "center",
15763
+ justifyContent: "space-between",
15764
+ padding: `${tokens.spacing.md} ${tokens.spacing.lg}`,
15765
+ background: tokens.colors.bgPrimary,
15766
+ borderBottom: `1px solid ${tokens.colors.borderPrimary}`,
15767
+ borderRadius: `${tokens.radius.xl} ${tokens.radius.xl} 0 0`,
15768
+ flexShrink: 0
15769
+ },
15770
+ children: [
15771
+ /* @__PURE__ */ jsx(
15772
+ "span",
15773
+ {
15774
+ style: {
15775
+ fontWeight: tokens.typography.fontWeightSemibold,
15776
+ color: tokens.colors.textPrimary,
15777
+ fontSize: tokens.typography.fontSizeMd
15778
+ },
15779
+ children: title
15780
+ }
15781
+ ),
15782
+ /* @__PURE__ */ jsx(
15783
+ "button",
15784
+ {
15785
+ onClick: toggle,
15786
+ onMouseEnter: () => setCloseHovered(true),
15787
+ onMouseLeave: () => setCloseHovered(false),
15788
+ style: {
15789
+ ...widgetStyles.closeBtn,
15790
+ ...closeHovered ? widgetStyles.closeBtnHover : {}
15791
+ },
15792
+ title: "Close chat",
15793
+ children: size === "full" ? /* @__PURE__ */ jsx(MinimizeIcon, {}) : /* @__PURE__ */ jsx(CloseIcon, {})
15794
+ }
15795
+ )
15796
+ ]
15797
+ }
15798
+ ),
15799
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }, children: /* @__PURE__ */ jsx(HustleChatInner, { ...chatProps }) })
15800
+ ]
15801
+ }
15802
+ ),
15803
+ (size !== "full" || !isOpen) && /* @__PURE__ */ jsxs(
15804
+ "button",
15805
+ {
15806
+ onClick: toggle,
15807
+ onMouseEnter: () => setIsHovered(true),
15808
+ onMouseLeave: () => setIsHovered(false),
15809
+ style: {
15810
+ ...widgetStyles.launcher,
15811
+ ...isHovered ? widgetStyles.launcherHover : {},
15812
+ ...positionStyles.launcher,
15813
+ ...launcherStyle,
15814
+ ...isOpen ? { opacity: 0, pointerEvents: "none" } : {}
15815
+ },
15816
+ title: isOpen ? "Close chat" : "Open chat",
15817
+ "aria-label": isOpen ? "Close chat" : "Open chat",
15818
+ children: [
15819
+ launcherIcon || /* @__PURE__ */ jsx(ChatIcon, {}),
15820
+ showBadge && badgeContent && !isOpen && /* @__PURE__ */ jsx("span", { style: widgetStyles.badge, children: badgeContent })
15821
+ ]
15822
+ }
15823
+ )
15824
+ ]
15825
+ }
15826
+ )
15827
+ ] });
15828
+ }
15829
+ function HustleChatInner(props) {
15830
+ return /* @__PURE__ */ jsx(HustleChat, { ...props, hideHeader: true });
15831
+ }
15832
+ function getPositionStyles(position, offset, isOpen, size) {
15833
+ const panelGap = 16;
15834
+ switch (position) {
15835
+ case "bottom-right":
15836
+ return {
15837
+ container: {
15838
+ bottom: `${offset.y}px`,
15839
+ right: `${offset.x}px`
15840
+ },
15841
+ launcher: {},
15842
+ panel: {
15843
+ bottom: `${56 + panelGap}px`,
15844
+ right: 0
15845
+ }
15846
+ };
15847
+ case "bottom-left":
15848
+ return {
15849
+ container: {
15850
+ bottom: `${offset.y}px`,
15851
+ left: `${offset.x}px`
15852
+ },
15853
+ launcher: {},
15854
+ panel: {
15855
+ bottom: `${56 + panelGap}px`,
15856
+ left: 0
15857
+ }
15858
+ };
15859
+ case "top-right":
15860
+ return {
15861
+ container: {
15862
+ top: `${offset.y}px`,
15863
+ right: `${offset.x}px`
15864
+ },
15865
+ launcher: {},
15866
+ panel: {
15867
+ top: `${56 + panelGap}px`,
15868
+ right: 0
15869
+ }
15870
+ };
15871
+ case "top-left":
15872
+ return {
15873
+ container: {
15874
+ top: `${offset.y}px`,
15875
+ left: `${offset.x}px`
15876
+ },
15877
+ launcher: {},
15878
+ panel: {
15879
+ top: `${56 + panelGap}px`,
15880
+ left: 0
15881
+ }
15882
+ };
15883
+ }
15884
+ }
15539
15885
 
15540
15886
  // src/utils/index.ts
15541
15887
  function formatFileSize(bytes) {
@@ -15564,6 +15910,6 @@ var DEFAULTS = {
15564
15910
  EMBLEM_MODAL_URL: "https://emblemvault.ai/connect"
15565
15911
  };
15566
15912
 
15567
- export { DEFAULTS, HustleChat, HustleProvider, MarkdownContent, STORAGE_KEYS, availablePlugins, debounce, formatFileSize, getAvailablePlugin, hydratePlugin, migrateFunPlugin, pluginRegistry, predictionMarketPlugin, tokens, useHustle, usePlugins };
15913
+ export { DEFAULTS, HustleChat, HustleChatWidget, HustleProvider, MarkdownContent, STORAGE_KEYS, availablePlugins, debounce, formatFileSize, getAvailablePlugin, hydratePlugin, migrateFunPlugin, pluginRegistry, predictionMarketPlugin, tokens, useHustle, usePlugins };
15568
15914
  //# sourceMappingURL=hustle-react.js.map
15569
15915
  //# sourceMappingURL=hustle-react.js.map