@alquimia-ai/ui 1.2.0 → 1.2.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 (104) hide show
  1. package/dist/components/hooks/index.js +1 -1
  2. package/dist/components/hooks/index.js.map +1 -1
  3. package/dist/components/hooks/index.mjs +1 -1
  4. package/dist/components/hooks/index.mjs.map +1 -1
  5. package/dist/components/molecules/index.d.mts +15 -2
  6. package/dist/components/molecules/index.d.ts +15 -2
  7. package/dist/components/molecules/index.js +837 -725
  8. package/dist/components/molecules/index.js.map +1 -1
  9. package/dist/components/molecules/index.mjs +819 -707
  10. package/dist/components/molecules/index.mjs.map +1 -1
  11. package/dist/components/organisms/index.js +260 -149
  12. package/dist/components/organisms/index.js.map +1 -1
  13. package/dist/components/organisms/index.mjs +258 -147
  14. package/dist/components/organisms/index.mjs.map +1 -1
  15. package/dist/index.d.mts +2 -2
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +265 -153
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +263 -151
  20. package/dist/index.mjs.map +1 -1
  21. package/package.json +52 -32
  22. package/src/components/atoms/index.ts +32 -0
  23. package/src/components/atoms/ui/alert.tsx +59 -0
  24. package/src/components/atoms/ui/aspect-ratio.tsx +7 -0
  25. package/src/components/atoms/ui/avatar.tsx +50 -0
  26. package/src/components/atoms/ui/badge.tsx +36 -0
  27. package/src/components/atoms/ui/breadcrumb.tsx +120 -0
  28. package/src/components/atoms/ui/button.tsx +56 -0
  29. package/src/components/atoms/ui/card.tsx +87 -0
  30. package/src/components/atoms/ui/checkbox.tsx +31 -0
  31. package/src/components/atoms/ui/command.tsx +155 -0
  32. package/src/components/atoms/ui/dialog.tsx +125 -0
  33. package/src/components/atoms/ui/drawer.tsx +119 -0
  34. package/src/components/atoms/ui/input.tsx +26 -0
  35. package/src/components/atoms/ui/label.tsx +26 -0
  36. package/src/components/atoms/ui/loader.tsx +52 -0
  37. package/src/components/atoms/ui/popover.tsx +31 -0
  38. package/src/components/atoms/ui/rich-text.tsx +19 -0
  39. package/src/components/atoms/ui/scroll-area.tsx +48 -0
  40. package/src/components/atoms/ui/select.tsx +160 -0
  41. package/src/components/atoms/ui/skeleton.tsx +15 -0
  42. package/src/components/atoms/ui/slider.tsx +29 -0
  43. package/src/components/atoms/ui/switch.tsx +30 -0
  44. package/src/components/atoms/ui/table.tsx +118 -0
  45. package/src/components/atoms/ui/tabs.tsx +56 -0
  46. package/src/components/atoms/ui/text-area/index.tsx +24 -0
  47. package/src/components/atoms/ui/textarea.tsx +25 -0
  48. package/src/components/atoms/ui/think-indicator.tsx +103 -0
  49. package/src/components/atoms/ui/toast.tsx +129 -0
  50. package/src/components/atoms/ui/toaster.tsx +38 -0
  51. package/src/components/atoms/ui/toggle.tsx +45 -0
  52. package/src/components/atoms/ui/typography/index.tsx +30 -0
  53. package/src/components/hooks/index.ts +4 -0
  54. package/src/components/hooks/use-document.tsx +44 -0
  55. package/src/components/hooks/use-resize-observer.ts +28 -0
  56. package/src/components/hooks/use-text-streaming.ts +63 -0
  57. package/src/components/hooks/use-toast.ts +194 -0
  58. package/src/components/index.ts +1 -0
  59. package/src/components/molecules/alert-dialog.tsx +141 -0
  60. package/src/components/molecules/assistant-button.tsx +148 -0
  61. package/src/components/molecules/call-out.tsx +163 -0
  62. package/src/components/molecules/carousel.tsx +262 -0
  63. package/src/components/molecules/documents/document-selector.tsx +79 -0
  64. package/src/components/molecules/documents/document-viewer.tsx +85 -0
  65. package/src/components/molecules/documents/index.ts +2 -0
  66. package/src/components/molecules/index.ts +11 -0
  67. package/src/components/molecules/navigation-menu.tsx +128 -0
  68. package/src/components/molecules/page-container.tsx +17 -0
  69. package/src/components/molecules/rating-comment.tsx +93 -0
  70. package/src/components/molecules/rating-stars.tsx +136 -0
  71. package/src/components/molecules/rating-thumbs.tsx +90 -0
  72. package/src/components/molecules/sidebar.tsx +107 -0
  73. package/src/components/molecules/sonner.tsx +30 -0
  74. package/src/components/molecules/viewers/index.ts +2 -0
  75. package/src/components/molecules/viewers/pdf-viewer.tsx +138 -0
  76. package/src/components/molecules/viewers/plain-text-viewer.tsx +40 -0
  77. package/src/components/organisms/assistant.tsx +271 -0
  78. package/src/components/organisms/index.ts +6 -0
  79. package/src/components/organisms/rating-dialog.tsx +104 -0
  80. package/src/components/organisms/speechToText.tsx +92 -0
  81. package/src/components/organisms/whisper.tsx +106 -0
  82. package/src/components/templates/cards/index.ts +2 -0
  83. package/src/components/templates/cards/with-image-heading-description-avatar.tsx +94 -0
  84. package/src/components/templates/cards/with-image-heading-description.tsx +63 -0
  85. package/src/components/templates/hero/index.tsx +39 -0
  86. package/src/components/templates/index.ts +4 -0
  87. package/src/components/templates/messages-window.tsx +15 -0
  88. package/src/components/templates/query-box.tsx +13 -0
  89. package/src/components/ui/input.tsx +25 -0
  90. package/src/components/ui/select.tsx +163 -0
  91. package/src/index.ts +7 -0
  92. package/src/lib/index.ts +1 -0
  93. package/src/lib/utils.ts +34 -0
  94. package/src/styles/call-out.css +153 -0
  95. package/src/styles/drawer.css +28 -0
  96. package/src/styles/globals.css +69 -0
  97. package/src/styles/prose.css +51 -0
  98. package/src/styles/ratings.css +27 -0
  99. package/src/styles/themes/base-alquimia.css +95 -0
  100. package/src/styles/themes/base-nordic.css +83 -0
  101. package/src/styles/themes/base-primary.css +85 -0
  102. package/src/styles/themes/base.css +8 -0
  103. package/src/types/index.ts +1 -0
  104. package/src/types/type.ts +76 -0
@@ -0,0 +1,148 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { useState, useEffect } from "react";
5
+ import { LucideIcon } from "lucide-react";
6
+ import { Button } from "../atoms";
7
+ import { cn } from "../../lib/utils";
8
+
9
+ interface Suggestion {
10
+ label: string;
11
+ icon: LucideIcon;
12
+ action: () => void;
13
+ }
14
+
15
+ interface AssistantSuggestionsProps {
16
+ suggestions: Suggestion[];
17
+ className?: string;
18
+ showSuggestions: boolean;
19
+ }
20
+
21
+ const AssistantSuggestions = React.forwardRef<
22
+ HTMLDivElement,
23
+ AssistantSuggestionsProps
24
+ >(({ suggestions, showSuggestions, className }, ref) => {
25
+ return (
26
+ <div
27
+ ref={ref}
28
+ className={cn(
29
+ "absolute bottom-[calc(100%+0.5rem)] right-0 pb-2 mb-4 alq--assistant-suggestions-container",
30
+ "transition-all duration-300",
31
+ showSuggestions
32
+ ? "opacity-100 translate-y-0"
33
+ : "opacity-0 translate-y-4",
34
+ className
35
+ )}
36
+ >
37
+ <div className="rounded-lg p-2 pr-0 w-64">
38
+ {suggestions.map((suggestion, index) => (
39
+ <button
40
+ key={index}
41
+ onClick={() => suggestion.action?.()}
42
+ style={{
43
+ transitionDelay: showSuggestions ? `${index * 50}ms` : "0ms",
44
+ }}
45
+ className={cn(
46
+ "bg-background flex items-center gap-2 border p-2 pr-4 my-2 rounded-full ml-auto",
47
+ "text-primary hover:bg-secondary",
48
+ "transition-all duration-100",
49
+ "origin-bottom w-fit",
50
+ "alq--assistant-suggestion",
51
+ showSuggestions
52
+ ? "opacity-100 translate-y-0"
53
+ : "opacity-0 translate-y-4"
54
+ )}
55
+ >
56
+ <suggestion.icon className="w-5 h-5 text-primary" />
57
+ <span className="text-xs whitespace-nowrap font-normal">
58
+ {suggestion.label}
59
+ </span>
60
+ </button>
61
+ ))}
62
+ </div>
63
+ </div>
64
+ );
65
+ });
66
+
67
+ AssistantSuggestions.displayName = "Suggestions";
68
+
69
+ interface AssistantButtonProps {
70
+ icon: LucideIcon;
71
+ clickAction: () => void;
72
+ className?: string;
73
+ suggestions?: Suggestion[];
74
+ }
75
+
76
+ export const AssistantButton = React.forwardRef<
77
+ HTMLDivElement,
78
+ AssistantButtonProps
79
+ >(
80
+ (
81
+ {
82
+ icon: Icon,
83
+ clickAction,
84
+ className,
85
+ suggestions = [],
86
+ },
87
+ ref
88
+ ) => {
89
+ const [isVisible, setIsVisible] = useState(false);
90
+ const [showSuggestions, setShowSuggestions] = useState(false);
91
+
92
+ const handleMouseEnter = () => {
93
+ setIsVisible(true);
94
+ requestAnimationFrame(() => {
95
+ requestAnimationFrame(() => {
96
+ setShowSuggestions(true);
97
+ });
98
+ });
99
+ };
100
+
101
+ const handleMouseLeave = () => {
102
+ setShowSuggestions(false);
103
+ setTimeout(() => setIsVisible(false), 300);
104
+ };
105
+
106
+ return (
107
+ <div
108
+ ref={ref}
109
+ className={cn(
110
+ "fixed bottom-8 right-4 z-50",
111
+ "alq--assistant-button-container",
112
+ className
113
+ )}
114
+ onMouseEnter={handleMouseEnter}
115
+ onMouseLeave={handleMouseLeave}
116
+ >
117
+ <div className="relative group">
118
+ {isVisible && suggestions.length > 0 && (
119
+ <AssistantSuggestions
120
+ suggestions={suggestions}
121
+ showSuggestions={showSuggestions}
122
+ />
123
+ )}
124
+ <Button
125
+ variant="outline"
126
+ size="sm"
127
+ className={cn(
128
+ "fixed hover:bg-opacity-70 transition-all duration-300",
129
+ "bottom-4 h-12 w-12 right-4 z-50 rounded-full p-2",
130
+ "shadow-md",
131
+ "hover:shadow-lg",
132
+ showSuggestions ? "scale-110" : "hover:scale-105",
133
+ "alq--assistant-button"
134
+ )}
135
+ onClick={clickAction}
136
+ >
137
+ <Icon
138
+ style={{ width: "22px", height: "22px" }}
139
+ className="text-primary"
140
+ />
141
+ </Button>
142
+ </div>
143
+ </div>
144
+ );
145
+ }
146
+ );
147
+
148
+ AssistantButton.displayName = "AssistantButton";
@@ -0,0 +1,163 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../../lib/utils";
4
+ import { RichText } from "../atoms";
5
+ import { Message } from "ai";
6
+ import { useTextStreaming } from "../hooks/use-text-streaming";
7
+
8
+ export interface CallOutMessage {
9
+ id: string;
10
+ role: "user" | "assistant";
11
+ content: string;
12
+ timestamp: Date;
13
+ }
14
+
15
+ interface CallOutActionProps {
16
+ label: string;
17
+ icon: React.ReactNode;
18
+ onClick: (message?: Message) => Promise<void>;
19
+ }
20
+
21
+ export interface CallOutProps extends React.HTMLAttributes<HTMLDivElement> {
22
+ message?: Message;
23
+ }
24
+
25
+ const CallOut = React.forwardRef<
26
+ HTMLDivElement,
27
+ React.HTMLAttributes<HTMLDivElement> & CallOutProps
28
+ >(({ className, children, role, ...props }, ref) => {
29
+ return (
30
+ <div
31
+ ref={ref}
32
+ className={cn("alq--callout-box", className)}
33
+ data-role={role}
34
+ {...props}
35
+ >
36
+ {children}
37
+ </div>
38
+ );
39
+ });
40
+
41
+ const CallOutDate = React.forwardRef<
42
+ HTMLDivElement,
43
+ React.HTMLAttributes<HTMLDivElement>
44
+ >(({ className, children, ...props }, ref) => {
45
+ return (
46
+ <div
47
+ ref={ref}
48
+ className={cn(
49
+ "alq--callout-date",
50
+ "text-sm text-muted-foreground",
51
+ className
52
+ )}
53
+ {...props}
54
+ >
55
+ {children}
56
+ </div>
57
+ );
58
+ });
59
+
60
+ const CallOutActions = React.forwardRef<
61
+ HTMLDivElement,
62
+ React.HTMLAttributes<HTMLDivElement> & {
63
+ actions: CallOutActionProps[];
64
+ role: "user" | "assistant";
65
+ message?: Message;
66
+ }
67
+ >(({ className, actions, role, message, ...props }, ref) => {
68
+ const [isLoading, setIsLoading] = React.useState(false);
69
+ const [isClicked, setIsClicked] = React.useState(false);
70
+
71
+ if (role === "user") {
72
+ return null;
73
+ }
74
+
75
+ return (
76
+ <div ref={ref} className={cn("alq--callout-actions", className)} {...props}>
77
+ {actions.map((action) => (
78
+ <button
79
+ key={action.label}
80
+ className={cn(
81
+ "alq--callout-action",
82
+ {
83
+ "alq--callout-animate-action": isLoading,
84
+ },
85
+ {
86
+ "alq--callout-clicked-action": isClicked,
87
+ }
88
+ )}
89
+ type="button"
90
+ title={action.label}
91
+ onClick={() => {
92
+ setIsLoading(true);
93
+ action.onClick(message).then(() => {
94
+ setIsLoading(false);
95
+ setIsClicked(true);
96
+ });
97
+ }}
98
+ >
99
+ <span
100
+ className={cn("alq-action-icon-wrapper", {
101
+ "alq--callout-animate-action": isLoading,
102
+ })}
103
+ >
104
+ {action.icon}
105
+ </span>
106
+ <label>{action.label}</label>
107
+ </button>
108
+ ))}
109
+ </div>
110
+ );
111
+ });
112
+
113
+ const CallOutResponse = React.forwardRef<
114
+ HTMLDivElement,
115
+ React.HTMLAttributes<HTMLDivElement> & {
116
+ role: Message["role"];
117
+ additionalInfo?: string;
118
+ isStreaming?: boolean;
119
+ handleIsTextStreaming?: (isStreaming: boolean) => void;
120
+ }
121
+ >(
122
+ (
123
+ { className, children, role, additionalInfo, isStreaming, handleIsTextStreaming, ...props },
124
+ ref
125
+ ) => {
126
+ const content = String(children || "");
127
+ const shouldStream = role === "assistant" && isStreaming;
128
+ const displayedContent = useTextStreaming(content, shouldStream ?? false, handleIsTextStreaming);
129
+
130
+ return (
131
+ <div
132
+ ref={ref}
133
+ data-role={role}
134
+ className={cn(
135
+ "alq--callout-response",
136
+ "max-w-none",
137
+ "text-foreground",
138
+ className
139
+ )}
140
+ {...props}
141
+ >
142
+ <RichText content={displayedContent} />
143
+ {additionalInfo && (
144
+ <div
145
+ className={cn(
146
+ "alq--callout-response-additional-info",
147
+ "mt-2 text-sm text-muted-foreground",
148
+ "border-t border-border pt-2"
149
+ )}
150
+ >
151
+ {additionalInfo}
152
+ </div>
153
+ )}
154
+ </div>
155
+ );
156
+ }
157
+ );
158
+
159
+ CallOut.displayName = "CallOut";
160
+ CallOutDate.displayName = "CallOutDate";
161
+ CallOutActions.displayName = "CallOutActions";
162
+ CallOutResponse.displayName = "CallOutResponse";
163
+ export { CallOut, CallOutDate, CallOutResponse, CallOutActions };
@@ -0,0 +1,262 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import useEmblaCarousel, {
5
+ type UseEmblaCarouselType,
6
+ } from "embla-carousel-react";
7
+ import { ArrowLeft, ArrowRight } from "lucide-react";
8
+
9
+ import { cn } from "../../lib/utils";
10
+ import { Button } from "../../components/atoms/ui/button";
11
+
12
+ type CarouselApi = UseEmblaCarouselType[1];
13
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
14
+ type CarouselOptions = UseCarouselParameters[0];
15
+ type CarouselPlugin = UseCarouselParameters[1];
16
+
17
+ type CarouselProps = {
18
+ opts?: CarouselOptions;
19
+ plugins?: CarouselPlugin;
20
+ orientation?: "horizontal" | "vertical";
21
+ setApi?: (api: CarouselApi) => void;
22
+ };
23
+
24
+ type CarouselContextProps = {
25
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0];
26
+ api: ReturnType<typeof useEmblaCarousel>[1];
27
+ scrollPrev: () => void;
28
+ scrollNext: () => void;
29
+ canScrollPrev: boolean;
30
+ canScrollNext: boolean;
31
+ } & CarouselProps;
32
+
33
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null);
34
+
35
+ function useCarousel() {
36
+ const context = React.useContext(CarouselContext);
37
+
38
+ if (!context) {
39
+ throw new Error("useCarousel must be used within a <Carousel />");
40
+ }
41
+
42
+ return context;
43
+ }
44
+
45
+ const Carousel = React.forwardRef<
46
+ HTMLDivElement,
47
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
48
+ >(
49
+ (
50
+ {
51
+ orientation = "horizontal",
52
+ opts,
53
+ setApi,
54
+ plugins,
55
+ className,
56
+ children,
57
+ ...props
58
+ },
59
+ ref
60
+ ) => {
61
+ const [carouselRef, api] = useEmblaCarousel(
62
+ {
63
+ ...opts,
64
+ axis: orientation === "horizontal" ? "x" : "y",
65
+ },
66
+ plugins
67
+ );
68
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
69
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
70
+
71
+ const onSelect = React.useCallback((api: CarouselApi) => {
72
+ if (!api) {
73
+ return;
74
+ }
75
+
76
+ setCanScrollPrev(api.canScrollPrev());
77
+ setCanScrollNext(api.canScrollNext());
78
+ }, []);
79
+
80
+ const scrollPrev = React.useCallback(() => {
81
+ api?.scrollPrev();
82
+ }, [api]);
83
+
84
+ const scrollNext = React.useCallback(() => {
85
+ api?.scrollNext();
86
+ }, [api]);
87
+
88
+ const handleKeyDown = React.useCallback(
89
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
90
+ if (event.key === "ArrowLeft") {
91
+ event.preventDefault();
92
+ scrollPrev();
93
+ } else if (event.key === "ArrowRight") {
94
+ event.preventDefault();
95
+ scrollNext();
96
+ }
97
+ },
98
+ [scrollPrev, scrollNext]
99
+ );
100
+
101
+ React.useEffect(() => {
102
+ if (!api || !setApi) {
103
+ return;
104
+ }
105
+
106
+ setApi(api);
107
+ }, [api, setApi]);
108
+
109
+ React.useEffect(() => {
110
+ if (!api) {
111
+ return;
112
+ }
113
+
114
+ onSelect(api);
115
+ api.on("reInit", onSelect);
116
+ api.on("select", onSelect);
117
+
118
+ return () => {
119
+ api?.off("select", onSelect);
120
+ };
121
+ }, [api, onSelect]);
122
+
123
+ return (
124
+ <CarouselContext.Provider
125
+ value={{
126
+ carouselRef,
127
+ api: api,
128
+ opts,
129
+ orientation:
130
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
131
+ scrollPrev,
132
+ scrollNext,
133
+ canScrollPrev,
134
+ canScrollNext,
135
+ }}
136
+ >
137
+ <div
138
+ ref={ref}
139
+ onKeyDownCapture={handleKeyDown}
140
+ className={cn("relative", className)}
141
+ role="region"
142
+ aria-roledescription="carousel"
143
+ {...props}
144
+ >
145
+ {children}
146
+ </div>
147
+ </CarouselContext.Provider>
148
+ );
149
+ }
150
+ );
151
+ Carousel.displayName = "Carousel";
152
+
153
+ const CarouselContent = React.forwardRef<
154
+ HTMLDivElement,
155
+ React.HTMLAttributes<HTMLDivElement>
156
+ >(({ className, ...props }, ref) => {
157
+ const { carouselRef, orientation } = useCarousel();
158
+
159
+ return (
160
+ <div ref={carouselRef} className="overflow-hidden">
161
+ <div
162
+ ref={ref}
163
+ className={cn(
164
+ "flex",
165
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
166
+ className
167
+ )}
168
+ {...props}
169
+ />
170
+ </div>
171
+ );
172
+ });
173
+ CarouselContent.displayName = "CarouselContent";
174
+
175
+ const CarouselItem = React.forwardRef<
176
+ HTMLDivElement,
177
+ React.HTMLAttributes<HTMLDivElement>
178
+ >(({ className, ...props }, ref) => {
179
+ const { orientation } = useCarousel();
180
+
181
+ return (
182
+ <div
183
+ ref={ref}
184
+ role="group"
185
+ aria-roledescription="slide"
186
+ className={cn(
187
+ "min-w-0 shrink-0 grow-0 basis-full",
188
+ orientation === "horizontal" ? "pl-4" : "pt-4",
189
+ className
190
+ )}
191
+ {...props}
192
+ />
193
+ );
194
+ });
195
+ CarouselItem.displayName = "CarouselItem";
196
+
197
+ const CarouselPrevious = React.forwardRef<
198
+ HTMLButtonElement,
199
+ React.ComponentProps<typeof Button>
200
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
201
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
202
+
203
+ return (
204
+ <Button
205
+ ref={ref}
206
+ variant={variant}
207
+ size={size}
208
+ className={cn(
209
+ "absolute h-8 w-8 rounded-full",
210
+ orientation === "horizontal"
211
+ ? "-left-12 top-1/2 -translate-y-1/2"
212
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
213
+ className
214
+ )}
215
+ disabled={!canScrollPrev}
216
+ onClick={scrollPrev}
217
+ {...props}
218
+ >
219
+ <ArrowLeft className="h-4 w-4" />
220
+ <span className="sr-only">Previous slide</span>
221
+ </Button>
222
+ );
223
+ });
224
+ CarouselPrevious.displayName = "CarouselPrevious";
225
+
226
+ const CarouselNext = React.forwardRef<
227
+ HTMLButtonElement,
228
+ React.ComponentProps<typeof Button>
229
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
230
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
231
+
232
+ return (
233
+ <Button
234
+ ref={ref}
235
+ variant={variant}
236
+ size={size}
237
+ className={cn(
238
+ "absolute h-8 w-8 rounded-full",
239
+ orientation === "horizontal"
240
+ ? "-right-12 top-1/2 -translate-y-1/2"
241
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
242
+ className
243
+ )}
244
+ disabled={!canScrollNext}
245
+ onClick={scrollNext}
246
+ {...props}
247
+ >
248
+ <ArrowRight className="h-4 w-4" />
249
+ <span className="sr-only">Next slide</span>
250
+ </Button>
251
+ );
252
+ });
253
+ CarouselNext.displayName = "CarouselNext";
254
+
255
+ export {
256
+ type CarouselApi,
257
+ Carousel,
258
+ CarouselContent,
259
+ CarouselItem,
260
+ CarouselPrevious,
261
+ CarouselNext,
262
+ };
@@ -0,0 +1,79 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ Select,
4
+ SelectTrigger,
5
+ SelectContent,
6
+ SelectItem,
7
+ SelectLabel,
8
+ SelectGroup,
9
+ SelectValue,
10
+ } from "../../atoms";
11
+ import { AlquimiaDocument, ActionResponse } from '../../../types/type';
12
+ import { ExternalLink } from "lucide-react";
13
+ import { DocumentViewer } from './document-viewer';
14
+
15
+ type DocumentSelectorProps = {
16
+ documents: AlquimiaDocument[];
17
+ getDocument: (id: string) => Promise<ActionResponse<Blob>>
18
+ logInfoMessage: (info: string, data: any) => void
19
+ }
20
+ export const DocumentSelector = ({ documents, getDocument, logInfoMessage }: DocumentSelectorProps) =>{
21
+ const [selectedDocName, setSelectedDocName] = useState<string | undefined>(undefined);
22
+ const [selectedDoc, setSelectedDoc] = useState<string | null>(null);
23
+ const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
24
+
25
+
26
+ const handleDocumentClick = (doc: any, event: React.MouseEvent) => {
27
+ event.stopPropagation();
28
+ setSelectedDoc(doc);
29
+ logInfoMessage("Selected document", {
30
+ documentId: doc.id,
31
+ documentName: doc.name,
32
+ });
33
+ setIsModalOpen(true);
34
+ };
35
+
36
+ const handleDrawerClose = () => {
37
+ setIsModalOpen(false)
38
+ }
39
+
40
+ return (
41
+ <div className="w-full md:w-4/6 alq--document-selector">
42
+ <Select value={selectedDocName} onValueChange={setSelectedDocName}>
43
+ <SelectTrigger aria-label="Documentos">
44
+ <SelectValue placeholder="Documentos" />
45
+ </SelectTrigger>
46
+ <SelectContent>
47
+ <SelectGroup>
48
+ <SelectLabel>Documents</SelectLabel>
49
+ {documents.map((doc) => (
50
+ <div key={doc.id} className="w-full flex relative">
51
+ <SelectItem className="w-full" key={doc.id} value={doc.id}>
52
+ <div className="flex justify-between w-full">
53
+ <p className="h-4 truncate mr-5">{doc.name}</p>
54
+ </div>
55
+ </SelectItem>
56
+ <div
57
+ onClick={(event) => handleDocumentClick(doc, event)}
58
+ className="bflex items-center text-gray-400 hover:text-gray-500 absolute right-0 mt-2"
59
+ aria-label={`Open ${doc.name}`}
60
+ >
61
+ <ExternalLink className="h-4 w-4 mx-2" />
62
+ </div>
63
+ </div>
64
+ ))}
65
+ </SelectGroup>
66
+ </SelectContent>
67
+ </Select>
68
+ {selectedDoc && (
69
+ <DocumentViewer
70
+ doc={selectedDoc}
71
+ docName={selectedDocName || ''}
72
+ isOpen={isModalOpen}
73
+ onClose={handleDrawerClose}
74
+ getDocument={getDocument}
75
+ />
76
+ )}
77
+ </div>
78
+ );
79
+ }