@greatapps/greatchat-ui 0.1.0 → 0.1.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.
@@ -0,0 +1,173 @@
1
+ import * as React from "react";
2
+ import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
3
+ import { Check, ChevronRight } from "lucide-react";
4
+
5
+ import { cn } from "../../lib/utils";
6
+
7
+ function DropdownMenu({
8
+ ...props
9
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
10
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
11
+ }
12
+
13
+ function DropdownMenuTrigger({
14
+ ...props
15
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
16
+ return (
17
+ <DropdownMenuPrimitive.Trigger
18
+ data-slot="dropdown-menu-trigger"
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ function DropdownMenuContent({
25
+ className,
26
+ align = "start",
27
+ sideOffset = 4,
28
+ ...props
29
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
30
+ return (
31
+ <DropdownMenuPrimitive.Portal>
32
+ <DropdownMenuPrimitive.Content
33
+ data-slot="dropdown-menu-content"
34
+ sideOffset={sideOffset}
35
+ align={align}
36
+ className={cn(
37
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-md p-1 shadow-md ring-1 duration-100 z-50 overflow-x-hidden overflow-y-auto",
38
+ className,
39
+ )}
40
+ {...props}
41
+ />
42
+ </DropdownMenuPrimitive.Portal>
43
+ );
44
+ }
45
+
46
+ function DropdownMenuGroup({
47
+ ...props
48
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
49
+ return (
50
+ <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
51
+ );
52
+ }
53
+
54
+ function DropdownMenuItem({
55
+ className,
56
+ inset,
57
+ variant = "default",
58
+ ...props
59
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
60
+ inset?: boolean;
61
+ variant?: "default" | "destructive";
62
+ }) {
63
+ return (
64
+ <DropdownMenuPrimitive.Item
65
+ data-slot="dropdown-menu-item"
66
+ data-inset={inset}
67
+ data-variant={variant}
68
+ className={cn(
69
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive gap-2 rounded-sm px-2 py-1.5 text-sm data-inset:pl-8 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
70
+ className,
71
+ )}
72
+ {...props}
73
+ />
74
+ );
75
+ }
76
+
77
+ function DropdownMenuCheckboxItem({
78
+ className,
79
+ children,
80
+ checked,
81
+ ...props
82
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
83
+ return (
84
+ <DropdownMenuPrimitive.CheckboxItem
85
+ data-slot="dropdown-menu-checkbox-item"
86
+ className={cn(
87
+ "focus:bg-accent focus:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
88
+ className,
89
+ )}
90
+ checked={checked}
91
+ {...props}
92
+ >
93
+ <span className="absolute right-2 flex items-center justify-center pointer-events-none">
94
+ <DropdownMenuPrimitive.ItemIndicator>
95
+ <Check />
96
+ </DropdownMenuPrimitive.ItemIndicator>
97
+ </span>
98
+ {children}
99
+ </DropdownMenuPrimitive.CheckboxItem>
100
+ );
101
+ }
102
+
103
+ function DropdownMenuSeparator({
104
+ className,
105
+ ...props
106
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
107
+ return (
108
+ <DropdownMenuPrimitive.Separator
109
+ data-slot="dropdown-menu-separator"
110
+ className={cn("bg-border -mx-1 my-1 h-px", className)}
111
+ {...props}
112
+ />
113
+ );
114
+ }
115
+
116
+ function DropdownMenuSub({
117
+ ...props
118
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
119
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
120
+ }
121
+
122
+ function DropdownMenuSubTrigger({
123
+ className,
124
+ inset,
125
+ children,
126
+ ...props
127
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
128
+ inset?: boolean;
129
+ }) {
130
+ return (
131
+ <DropdownMenuPrimitive.SubTrigger
132
+ data-slot="dropdown-menu-sub-trigger"
133
+ data-inset={inset}
134
+ className={cn(
135
+ "focus:bg-accent focus:text-accent-foreground data-open:bg-accent gap-2 rounded-sm px-2 py-1.5 text-sm data-inset:pl-8 [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
136
+ className,
137
+ )}
138
+ {...props}
139
+ >
140
+ {children}
141
+ <ChevronRight className="ml-auto" />
142
+ </DropdownMenuPrimitive.SubTrigger>
143
+ );
144
+ }
145
+
146
+ function DropdownMenuSubContent({
147
+ className,
148
+ ...props
149
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
150
+ return (
151
+ <DropdownMenuPrimitive.SubContent
152
+ data-slot="dropdown-menu-sub-content"
153
+ className={cn(
154
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 bg-popover text-popover-foreground min-w-[96px] rounded-md p-1 shadow-lg ring-1 duration-100 z-50 overflow-hidden",
155
+ className,
156
+ )}
157
+ {...props}
158
+ />
159
+ );
160
+ }
161
+
162
+ export {
163
+ DropdownMenu,
164
+ DropdownMenuTrigger,
165
+ DropdownMenuContent,
166
+ DropdownMenuGroup,
167
+ DropdownMenuItem,
168
+ DropdownMenuCheckboxItem,
169
+ DropdownMenuSeparator,
170
+ DropdownMenuSub,
171
+ DropdownMenuSubTrigger,
172
+ DropdownMenuSubContent,
173
+ };
@@ -0,0 +1,156 @@
1
+ import * as React from "react";
2
+ import { Select as SelectPrimitive } from "radix-ui";
3
+ import { Check, ChevronDown, ChevronUp, ChevronsUpDown } from "lucide-react";
4
+
5
+ import { cn } from "../../lib/utils";
6
+
7
+ function Select({
8
+ ...props
9
+ }: React.ComponentProps<typeof SelectPrimitive.Root>) {
10
+ return <SelectPrimitive.Root data-slot="select" {...props} />;
11
+ }
12
+
13
+ function SelectGroup({
14
+ className,
15
+ ...props
16
+ }: React.ComponentProps<typeof SelectPrimitive.Group>) {
17
+ return (
18
+ <SelectPrimitive.Group
19
+ data-slot="select-group"
20
+ className={cn("scroll-my-1 p-1", className)}
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ function SelectValue({
27
+ ...props
28
+ }: React.ComponentProps<typeof SelectPrimitive.Value>) {
29
+ return <SelectPrimitive.Value data-slot="select-value" {...props} />;
30
+ }
31
+
32
+ function SelectTrigger({
33
+ className,
34
+ size = "default",
35
+ children,
36
+ ...props
37
+ }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
38
+ size?: "sm" | "default";
39
+ }) {
40
+ return (
41
+ <SelectPrimitive.Trigger
42
+ data-slot="select-trigger"
43
+ data-size={size}
44
+ className={cn(
45
+ "border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 gap-1.5 rounded-md border bg-transparent py-2 pr-2 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] focus-visible:ring-3 data-[size=default]:h-9 data-[size=sm]:h-8 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
46
+ className,
47
+ )}
48
+ {...props}
49
+ >
50
+ {children}
51
+ <SelectPrimitive.Icon asChild>
52
+ <ChevronsUpDown className="text-muted-foreground size-4 pointer-events-none" />
53
+ </SelectPrimitive.Icon>
54
+ </SelectPrimitive.Trigger>
55
+ );
56
+ }
57
+
58
+ function SelectContent({
59
+ className,
60
+ children,
61
+ position = "item-aligned",
62
+ align = "center",
63
+ ...props
64
+ }: React.ComponentProps<typeof SelectPrimitive.Content>) {
65
+ return (
66
+ <SelectPrimitive.Portal>
67
+ <SelectPrimitive.Content
68
+ data-slot="select-content"
69
+ className={cn(
70
+ "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-md shadow-md ring-1 duration-100 relative z-50 max-h-(--radix-select-content-available-height) overflow-x-hidden overflow-y-auto",
71
+ position === "popper" &&
72
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
73
+ className,
74
+ )}
75
+ position={position}
76
+ align={align}
77
+ {...props}
78
+ >
79
+ <SelectScrollUpButton />
80
+ <SelectPrimitive.Viewport>{children}</SelectPrimitive.Viewport>
81
+ <SelectScrollDownButton />
82
+ </SelectPrimitive.Content>
83
+ </SelectPrimitive.Portal>
84
+ );
85
+ }
86
+
87
+ function SelectItem({
88
+ className,
89
+ children,
90
+ ...props
91
+ }: React.ComponentProps<typeof SelectPrimitive.Item>) {
92
+ return (
93
+ <SelectPrimitive.Item
94
+ data-slot="select-item"
95
+ className={cn(
96
+ "focus:bg-accent focus:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
97
+ className,
98
+ )}
99
+ {...props}
100
+ >
101
+ <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
102
+ <SelectPrimitive.ItemIndicator>
103
+ <Check className="pointer-events-none" />
104
+ </SelectPrimitive.ItemIndicator>
105
+ </span>
106
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
107
+ </SelectPrimitive.Item>
108
+ );
109
+ }
110
+
111
+ function SelectScrollUpButton({
112
+ className,
113
+ ...props
114
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
115
+ return (
116
+ <SelectPrimitive.ScrollUpButton
117
+ data-slot="select-scroll-up-button"
118
+ className={cn(
119
+ "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4",
120
+ className,
121
+ )}
122
+ {...props}
123
+ >
124
+ <ChevronUp />
125
+ </SelectPrimitive.ScrollUpButton>
126
+ );
127
+ }
128
+
129
+ function SelectScrollDownButton({
130
+ className,
131
+ ...props
132
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
133
+ return (
134
+ <SelectPrimitive.ScrollDownButton
135
+ data-slot="select-scroll-down-button"
136
+ className={cn(
137
+ "bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4",
138
+ className,
139
+ )}
140
+ {...props}
141
+ >
142
+ <ChevronDown />
143
+ </SelectPrimitive.ScrollDownButton>
144
+ );
145
+ }
146
+
147
+ export {
148
+ Select,
149
+ SelectContent,
150
+ SelectGroup,
151
+ SelectItem,
152
+ SelectScrollDownButton,
153
+ SelectScrollUpButton,
154
+ SelectTrigger,
155
+ SelectValue,
156
+ };
@@ -0,0 +1,16 @@
1
+ import { cn } from "../../lib/utils";
2
+
3
+ function Skeleton({
4
+ className,
5
+ ...props
6
+ }: React.ComponentProps<"div">) {
7
+ return (
8
+ <div
9
+ data-slot="skeleton"
10
+ className={cn("bg-muted rounded-md animate-pulse", className)}
11
+ {...props}
12
+ />
13
+ );
14
+ }
15
+
16
+ export { Skeleton };
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../../lib/utils";
4
+
5
+ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6
+ return (
7
+ <textarea
8
+ data-slot="textarea"
9
+ className={cn(
10
+ "border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] focus-visible:ring-3 md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ export { Textarea };
package/src/index.ts CHANGED
@@ -16,3 +16,8 @@ export type { GchatClientConfig } from "./client";
16
16
 
17
17
  // Utils
18
18
  export { cn } from "./lib/utils";
19
+ export { groupMessagesByDate, formatDateGroup, formatMessageTime } from "./utils";
20
+
21
+ // Components
22
+ export { ChatView, ChatInput, MessageBubble } from "./components";
23
+ export type { ChatViewProps, ChatInputProps, MessageBubbleProps } from "./components";
@@ -0,0 +1,13 @@
1
+ import { format, isToday, isYesterday } from "date-fns";
2
+ import { ptBR } from "date-fns/locale";
3
+
4
+ export function formatDateGroup(dateStr: string): string {
5
+ const date = new Date(dateStr);
6
+ if (isToday(date)) return "Hoje";
7
+ if (isYesterday(date)) return "Ontem";
8
+ return format(date, "dd 'de' MMMM", { locale: ptBR });
9
+ }
10
+
11
+ export function formatMessageTime(dateStr: string): string {
12
+ return format(new Date(dateStr), "HH:mm");
13
+ }
@@ -0,0 +1,22 @@
1
+ import type { InboxMessage } from "../types";
2
+
3
+ /**
4
+ * Groups messages by date. Assumes messages are pre-sorted by datetime_add (ascending).
5
+ */
6
+ export function groupMessagesByDate(
7
+ messages: InboxMessage[],
8
+ ): { date: string; messages: InboxMessage[] }[] {
9
+ const groups: { date: string; messages: InboxMessage[] }[] = [];
10
+ let currentDate = "";
11
+
12
+ for (const msg of messages) {
13
+ const dateStr = msg.datetime_add.split("T")[0];
14
+ if (dateStr !== currentDate) {
15
+ currentDate = dateStr;
16
+ groups.push({ date: msg.datetime_add, messages: [] });
17
+ }
18
+ groups[groups.length - 1].messages.push(msg);
19
+ }
20
+
21
+ return groups;
22
+ }
@@ -0,0 +1,2 @@
1
+ export { groupMessagesByDate } from "./group-messages";
2
+ export { formatDateGroup, formatMessageTime } from "./format-date";