@connectycube/react-ui-kit 0.0.20 → 0.0.22
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/configs/dependencies.json +19 -4
- package/configs/imports.json +7 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/components/attachment.d.ts +5 -6
- package/dist/types/components/attachment.d.ts.map +1 -1
- package/dist/types/components/chat-bubble.d.ts +32 -0
- package/dist/types/components/chat-bubble.d.ts.map +1 -0
- package/dist/types/components/chat-input.d.ts +27 -0
- package/dist/types/components/chat-input.d.ts.map +1 -0
- package/dist/types/components/chat-list.d.ts +30 -0
- package/dist/types/components/chat-list.d.ts.map +1 -0
- package/dist/types/components/checkbox.d.ts +11 -0
- package/dist/types/components/checkbox.d.ts.map +1 -0
- package/dist/types/components/dialogs-list.d.ts +14 -0
- package/dist/types/components/dialogs-list.d.ts.map +1 -0
- package/dist/types/components/file-picker.d.ts +1 -1
- package/dist/types/components/file-picker.d.ts.map +1 -1
- package/dist/types/components/placeholder-text.d.ts.map +1 -1
- package/dist/types/components/quick-actions.d.ts +14 -0
- package/dist/types/components/quick-actions.d.ts.map +1 -0
- package/dist/types/components/status-call.d.ts +8 -0
- package/dist/types/components/status-call.d.ts.map +1 -0
- package/dist/types/index.d.ts +7 -2
- package/dist/types/index.d.ts.map +1 -1
- package/gen/components/attachment.jsx +6 -6
- package/gen/components/button.jsx +1 -1
- package/gen/components/{chat-message.jsx → chat-bubble.jsx} +69 -48
- package/gen/components/chat-input.jsx +152 -0
- package/gen/components/chat-list.jsx +151 -0
- package/gen/components/checkbox.jsx +30 -0
- package/gen/components/dialog-item.jsx +1 -1
- package/gen/components/dialogs-list.jsx +73 -0
- package/gen/components/dismiss-layer.jsx +1 -1
- package/gen/components/file-picker.jsx +2 -2
- package/gen/components/placeholder-text.jsx +5 -1
- package/gen/components/quick-actions.jsx +62 -0
- package/gen/components/search.jsx +1 -1
- package/gen/components/status-call.jsx +18 -0
- package/gen/components/stream-view.jsx +8 -8
- package/gen/index.js +14 -2
- package/package.json +11 -8
- package/src/components/attachment.tsx +16 -14
- package/src/components/button.tsx +1 -1
- package/src/components/chat-bubble.tsx +176 -0
- package/src/components/chat-input.tsx +172 -0
- package/src/components/chat-list.tsx +164 -0
- package/src/components/checkbox.tsx +40 -0
- package/src/components/connectycube-ui/chat-input.tsx +174 -0
- package/src/components/dialog-item.tsx +1 -1
- package/src/components/dialogs-list.tsx +84 -0
- package/src/components/dismiss-layer.tsx +1 -1
- package/src/components/file-picker.tsx +3 -3
- package/src/components/placeholder-text.tsx +5 -1
- package/src/components/quick-actions.tsx +74 -0
- package/src/components/search.tsx +1 -1
- package/src/components/status-call.tsx +23 -0
- package/src/components/stream-view.tsx +8 -8
- package/src/index.ts +17 -2
- package/dist/types/components/call-message.d.ts +0 -17
- package/dist/types/components/call-message.d.ts.map +0 -1
- package/dist/types/components/chat-message.d.ts +0 -30
- package/dist/types/components/chat-message.d.ts.map +0 -1
- package/gen/components/call-message.jsx +0 -62
- package/src/components/call-message.tsx +0 -75
- package/src/components/chat-message.tsx +0 -138
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
2
|
+
import TextareaAutosize from 'react-textarea-autosize';
|
|
3
|
+
import { SendHorizontal } from 'lucide-react';
|
|
4
|
+
import { Label } from './label';
|
|
5
|
+
import { cn } from './utils';
|
|
6
|
+
|
|
7
|
+
function ChatInputSendBase({ onSend = () => {}, iconElement, iconProps, ...props }, ref) {
|
|
8
|
+
return (
|
|
9
|
+
<Label
|
|
10
|
+
ref={ref}
|
|
11
|
+
{...props}
|
|
12
|
+
onClick={onSend}
|
|
13
|
+
className={cn('group rounded-full py-2 pl-2.5 pr-1.5 cursor-pointer bg-ring/90 hover:bg-ring', props?.className)}
|
|
14
|
+
>
|
|
15
|
+
{iconElement || (
|
|
16
|
+
<SendHorizontal
|
|
17
|
+
{...iconProps}
|
|
18
|
+
className={cn(
|
|
19
|
+
'text-background group-hover:scale-110 transition-all duration-200 ease-out',
|
|
20
|
+
iconProps?.className
|
|
21
|
+
)}
|
|
22
|
+
/>
|
|
23
|
+
)}
|
|
24
|
+
</Label>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const ChatInputSend = forwardRef(ChatInputSendBase);
|
|
29
|
+
|
|
30
|
+
ChatInputSend.displayName = 'ChatInputSend';
|
|
31
|
+
|
|
32
|
+
function ChatInputBase(
|
|
33
|
+
{
|
|
34
|
+
pending = false,
|
|
35
|
+
draft,
|
|
36
|
+
onSend = () => {},
|
|
37
|
+
onDraft = () => {},
|
|
38
|
+
onTyping = () => {},
|
|
39
|
+
onHeightGrow = () => {},
|
|
40
|
+
hideChatInputSend = false,
|
|
41
|
+
chatInputSendProps,
|
|
42
|
+
containerProps,
|
|
43
|
+
children,
|
|
44
|
+
...props
|
|
45
|
+
},
|
|
46
|
+
ref
|
|
47
|
+
) {
|
|
48
|
+
const [value, setValue] = useState();
|
|
49
|
+
const textareaRef = useRef(null);
|
|
50
|
+
const textareaHeightRef = useRef(0);
|
|
51
|
+
const typingRef = useRef(false);
|
|
52
|
+
const typingTimeoutRef = useRef(undefined);
|
|
53
|
+
const handleStopTyping = () => {
|
|
54
|
+
typingRef.current = false;
|
|
55
|
+
onTyping(false);
|
|
56
|
+
|
|
57
|
+
if (typingTimeoutRef.current) {
|
|
58
|
+
clearTimeout(typingTimeoutRef.current);
|
|
59
|
+
typingTimeoutRef.current = undefined;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const handleOnChange = (event) => {
|
|
63
|
+
const { data } = event.nativeEvent;
|
|
64
|
+
|
|
65
|
+
setValue(event.target.value);
|
|
66
|
+
|
|
67
|
+
if (!typingRef.current && typeof data === 'string' && data.length > 0) {
|
|
68
|
+
typingRef.current = true;
|
|
69
|
+
onTyping(true);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
clearTimeout(typingTimeoutRef.current);
|
|
73
|
+
typingTimeoutRef.current = setTimeout(handleStopTyping, 5000);
|
|
74
|
+
};
|
|
75
|
+
const handleOnSend = async () => {
|
|
76
|
+
handleStopTyping();
|
|
77
|
+
|
|
78
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
79
|
+
onSend(value.trim());
|
|
80
|
+
setValue('');
|
|
81
|
+
textareaRef.current?.focus();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const handleOnHeightChange = (height, meta) => {
|
|
85
|
+
if (!height && !meta) return;
|
|
86
|
+
|
|
87
|
+
if (height !== textareaHeightRef.current) {
|
|
88
|
+
const shift = height > textareaHeightRef.current ? meta.rowHeight : -meta.rowHeight;
|
|
89
|
+
|
|
90
|
+
onHeightGrow({
|
|
91
|
+
height,
|
|
92
|
+
shift,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
textareaHeightRef.current = height;
|
|
97
|
+
};
|
|
98
|
+
const handleOnKeyDown = (event) => {
|
|
99
|
+
if (event.key === 'Enter' && event.shiftKey === false) {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
props.onKeyDown?.(event);
|
|
102
|
+
handleOnSend();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const textarea = textareaRef.current;
|
|
108
|
+
|
|
109
|
+
handleStopTyping();
|
|
110
|
+
queueMicrotask(() => setValue(draft));
|
|
111
|
+
textarea?.focus();
|
|
112
|
+
|
|
113
|
+
return () => {
|
|
114
|
+
onDraft(textarea?.value);
|
|
115
|
+
};
|
|
116
|
+
}, [props.key]);
|
|
117
|
+
|
|
118
|
+
useImperativeHandle(ref, () => textareaRef.current || {}, []);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div {...containerProps} className={cn('flex items-end gap-2', containerProps?.className)}>
|
|
122
|
+
<TextareaAutosize
|
|
123
|
+
name="textarea-autosize"
|
|
124
|
+
ref={textareaRef}
|
|
125
|
+
autoFocus
|
|
126
|
+
minRows={1}
|
|
127
|
+
maxRows={8}
|
|
128
|
+
{...props}
|
|
129
|
+
value={value}
|
|
130
|
+
onChange={handleOnChange}
|
|
131
|
+
onKeyDown={handleOnKeyDown}
|
|
132
|
+
onHeightChange={handleOnHeightChange}
|
|
133
|
+
className={cn(
|
|
134
|
+
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-11 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
|
135
|
+
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring',
|
|
136
|
+
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
|
137
|
+
'resize-none py-2.5',
|
|
138
|
+
pending && 'pointer-events-none bg-muted-foreground border-ring ring-ring/50 animate-pulse',
|
|
139
|
+
props?.className
|
|
140
|
+
)}
|
|
141
|
+
/>
|
|
142
|
+
{!hideChatInputSend && <ChatInputSend onSend={handleOnSend} {...chatInputSendProps} />}
|
|
143
|
+
{children}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const ChatInput = forwardRef(ChatInputBase);
|
|
149
|
+
|
|
150
|
+
ChatInput.displayName = 'ChatInput';
|
|
151
|
+
|
|
152
|
+
export { ChatInput };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import React, { useRef, useEffect, useState, useImperativeHandle, forwardRef, useCallback } from 'react';
|
|
2
|
+
import { Virtualizer } from 'virtua';
|
|
3
|
+
import { FilePickerDropzone } from './file-picker';
|
|
4
|
+
import { QuickActions } from './quick-actions';
|
|
5
|
+
import { Spinner } from './spinner';
|
|
6
|
+
import { cn } from './utils';
|
|
7
|
+
|
|
8
|
+
function DefaultChatListWrapper({ children, ...props }) {
|
|
9
|
+
return (
|
|
10
|
+
<div {...props} className={cn('relative size-full min-h-0', props?.className)}>
|
|
11
|
+
{children}
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function ChatListBase(
|
|
17
|
+
{
|
|
18
|
+
key,
|
|
19
|
+
loading = false,
|
|
20
|
+
textareaMeasurement,
|
|
21
|
+
offsetToReach = 200,
|
|
22
|
+
onScrollStartReached,
|
|
23
|
+
onScrollEndReached,
|
|
24
|
+
onListReset,
|
|
25
|
+
onListCreate,
|
|
26
|
+
onListGrow,
|
|
27
|
+
enableFilePickerDropzone = true,
|
|
28
|
+
containerProps,
|
|
29
|
+
filePickerDropzoneProps,
|
|
30
|
+
quickActionsProps,
|
|
31
|
+
children,
|
|
32
|
+
quickActionsVisible,
|
|
33
|
+
minItemsCount = 1,
|
|
34
|
+
...props
|
|
35
|
+
},
|
|
36
|
+
ref
|
|
37
|
+
) {
|
|
38
|
+
const ChatListWrapper = enableFilePickerDropzone ? FilePickerDropzone : DefaultChatListWrapper;
|
|
39
|
+
const chatListWrapperProps = enableFilePickerDropzone ? filePickerDropzoneProps : containerProps;
|
|
40
|
+
const itemsCount = Array.isArray(children) ? children.length : Array.isArray(props.data) ? props.data.length : 0;
|
|
41
|
+
const itemsCountRef = useRef(0);
|
|
42
|
+
const keyRef = useRef(undefined);
|
|
43
|
+
const [shouldPrependMessages, setShouldPrependMessages] = useState(false);
|
|
44
|
+
const [isPreparing, setIsPreparing] = useState(false);
|
|
45
|
+
const virtuaRef = useRef(null);
|
|
46
|
+
const prepareNextVirtualizer = () => {
|
|
47
|
+
queueMicrotask(() => {
|
|
48
|
+
setIsPreparing(true);
|
|
49
|
+
queueMicrotask(() => setIsPreparing(false));
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
const handleOnScroll = async (offset) => {
|
|
53
|
+
props.onScroll?.(offset);
|
|
54
|
+
|
|
55
|
+
if (!virtuaRef.current) return;
|
|
56
|
+
|
|
57
|
+
if (typeof onScrollStartReached === 'function' && offset < offsetToReach) {
|
|
58
|
+
setShouldPrependMessages(true);
|
|
59
|
+
onScrollStartReached();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
typeof onScrollEndReached === 'function' &&
|
|
64
|
+
virtuaRef.current.viewportSize + offset + offsetToReach > virtuaRef.current.scrollSize
|
|
65
|
+
) {
|
|
66
|
+
onScrollEndReached();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const scrollToBottom = useCallback(
|
|
70
|
+
(force = false) => {
|
|
71
|
+
if (!virtuaRef.current) return;
|
|
72
|
+
|
|
73
|
+
if (force || virtuaRef.current.scrollSize - virtuaRef.current.scrollOffset < virtuaRef.current.viewportSize * 2) {
|
|
74
|
+
queueMicrotask(() =>
|
|
75
|
+
virtuaRef.current?.scrollToIndex(itemsCount - 1, {
|
|
76
|
+
align: 'start',
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
[itemsCount]
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (key !== keyRef.current) {
|
|
86
|
+
prepareNextVirtualizer();
|
|
87
|
+
onListReset?.(keyRef.current, key);
|
|
88
|
+
keyRef.current = key;
|
|
89
|
+
itemsCountRef.current = 0;
|
|
90
|
+
}
|
|
91
|
+
}, [key, onListReset]);
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (itemsCountRef.current === 0 && itemsCount > 0) {
|
|
95
|
+
prepareNextVirtualizer();
|
|
96
|
+
onListCreate?.();
|
|
97
|
+
itemsCountRef.current = itemsCount;
|
|
98
|
+
scrollToBottom(true);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (itemsCount > itemsCountRef.current) {
|
|
102
|
+
onListGrow?.();
|
|
103
|
+
itemsCountRef.current = itemsCount;
|
|
104
|
+
|
|
105
|
+
if (shouldPrependMessages) {
|
|
106
|
+
queueMicrotask(() => setShouldPrependMessages(false));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
scrollToBottom();
|
|
110
|
+
}
|
|
111
|
+
}, [itemsCount, shouldPrependMessages, onListCreate, onListGrow, scrollToBottom]);
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
virtuaRef.current?.scrollBy(textareaMeasurement.shift);
|
|
115
|
+
}, [textareaMeasurement]);
|
|
116
|
+
|
|
117
|
+
useImperativeHandle(
|
|
118
|
+
ref,
|
|
119
|
+
() => ({
|
|
120
|
+
...(virtuaRef.current || {}),
|
|
121
|
+
scrollToBottom,
|
|
122
|
+
}),
|
|
123
|
+
[scrollToBottom]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (isPreparing) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (minItemsCount >= itemsCount) {
|
|
131
|
+
return quickActionsVisible ? <QuickActions {...quickActionsProps} /> : <Spinner loading layout="centered" />;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<ChatListWrapper {...chatListWrapperProps}>
|
|
136
|
+
<Spinner loading={loading} className="my-5" />
|
|
137
|
+
<div className="flex flex-col h-full overflow-y-auto px-2">
|
|
138
|
+
<div className="grow" />
|
|
139
|
+
<Virtualizer ref={virtuaRef} onScroll={handleOnScroll} shift={shouldPrependMessages} {...props}>
|
|
140
|
+
{children}
|
|
141
|
+
</Virtualizer>
|
|
142
|
+
</div>
|
|
143
|
+
</ChatListWrapper>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const ChatList = forwardRef(ChatListBase);
|
|
148
|
+
|
|
149
|
+
ChatList.displayName = 'ChatList';
|
|
150
|
+
|
|
151
|
+
export { ChatList };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
|
3
|
+
import { CheckIcon } from 'lucide-react';
|
|
4
|
+
import { cn } from './utils';
|
|
5
|
+
|
|
6
|
+
function CheckboxBase({ iconElement, iconProps, indicatorProps, ...props }, ref) {
|
|
7
|
+
return (
|
|
8
|
+
<CheckboxPrimitive.Root
|
|
9
|
+
ref={ref}
|
|
10
|
+
{...props}
|
|
11
|
+
className={cn(
|
|
12
|
+
'peer border-input dark:bg-input/30 data-[state=checked]:bg-ring data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-ring data-[state=checked]:border-ring focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
|
13
|
+
props?.className
|
|
14
|
+
)}
|
|
15
|
+
>
|
|
16
|
+
<CheckboxPrimitive.Indicator
|
|
17
|
+
{...indicatorProps}
|
|
18
|
+
className={cn('flex items-center justify-center text-current transition-none', indicatorProps?.className)}
|
|
19
|
+
>
|
|
20
|
+
{iconElement || <CheckIcon {...iconProps} className={cn('size-3.5', iconProps?.className)} />}
|
|
21
|
+
</CheckboxPrimitive.Indicator>
|
|
22
|
+
</CheckboxPrimitive.Root>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const Checkbox = forwardRef(CheckboxBase);
|
|
27
|
+
|
|
28
|
+
Checkbox.displayName = 'Checkbox';
|
|
29
|
+
|
|
30
|
+
export { Checkbox };
|
|
@@ -60,7 +60,7 @@ function DialogItemBase(
|
|
|
60
60
|
onClick={onSelect}
|
|
61
61
|
className={cn(
|
|
62
62
|
'flex items-start gap-2 px-2 flex-1 cursor-pointer',
|
|
63
|
-
'transition-colors duration-200 ease-
|
|
63
|
+
'transition-colors duration-200 ease-out',
|
|
64
64
|
`${selected ? 'border-l-[0.25em] pl-1 border-l-ring bg-ring/20' : 'hover:bg-ring/5'}`,
|
|
65
65
|
props?.className
|
|
66
66
|
)}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
|
2
|
+
import { VList } from 'virtua';
|
|
3
|
+
import { PlaceholderText } from './placeholder-text';
|
|
4
|
+
|
|
5
|
+
const PendingItem = () => (
|
|
6
|
+
<div className="flex flex-row gap-2 mx-2 border-b">
|
|
7
|
+
<div className="size-13 my-2 rounded-full bg-muted animate-pulse" />
|
|
8
|
+
<div className="flex-1 text-muted">
|
|
9
|
+
<div className="flex flex-row items-center justify-between h-6">
|
|
10
|
+
<div className="w-2/3 h-4 rounded-full bg-muted animate-pulse" />
|
|
11
|
+
<div className="w-1/6 h-3.5 rounded-full bg-muted animate-pulse" />
|
|
12
|
+
</div>
|
|
13
|
+
<div className="flex flex-col items-start justify-around h-10">
|
|
14
|
+
<div className="w-7/8 h-3.5 rounded-full bg-muted animate-pulse" />
|
|
15
|
+
<div className="w-5/6 h-3.5 rounded-full bg-muted animate-pulse" />
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
function DialogsListBase(
|
|
22
|
+
{
|
|
23
|
+
children,
|
|
24
|
+
pending,
|
|
25
|
+
pendingListLength = 5,
|
|
26
|
+
offsetToReach = 50,
|
|
27
|
+
placeholderVisible,
|
|
28
|
+
placeholderTitles = ['No dialogs yet.', 'Start a conversation!'],
|
|
29
|
+
onScrollStartReached,
|
|
30
|
+
onScrollEndReached,
|
|
31
|
+
...props
|
|
32
|
+
},
|
|
33
|
+
ref
|
|
34
|
+
) {
|
|
35
|
+
const vListRef = useRef(null);
|
|
36
|
+
const skeletonList = Array.from({
|
|
37
|
+
length: pendingListLength,
|
|
38
|
+
}).map((_, i) => <PendingItem key={`pending_dialog_item_${i}`} />);
|
|
39
|
+
const handleOnScroll = async (offset) => {
|
|
40
|
+
props.onScroll?.(offset);
|
|
41
|
+
|
|
42
|
+
if (!vListRef.current) return;
|
|
43
|
+
|
|
44
|
+
if (typeof onScrollStartReached === 'function' && offset < offsetToReach) {
|
|
45
|
+
onScrollStartReached();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
typeof onScrollEndReached === 'function' &&
|
|
50
|
+
vListRef.current.viewportSize + offset + offsetToReach > vListRef.current.scrollSize
|
|
51
|
+
) {
|
|
52
|
+
onScrollEndReached();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
useImperativeHandle(ref, () => vListRef.current || {}, []);
|
|
57
|
+
|
|
58
|
+
if (placeholderVisible) {
|
|
59
|
+
return <PlaceholderText titles={placeholderTitles} className="text-base text-muted" />;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<VList ref={vListRef} onScroll={handleOnScroll} className="size-full overflow-y-scroll" {...props}>
|
|
64
|
+
{pending ? skeletonList : children}
|
|
65
|
+
</VList>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const DialogsList = forwardRef(DialogsListBase);
|
|
70
|
+
|
|
71
|
+
DialogsList.displayName = 'DialogsList';
|
|
72
|
+
|
|
73
|
+
export { DialogsList };
|
|
@@ -73,7 +73,7 @@ function FilePickerInputBase(
|
|
|
73
73
|
{...labelProps}
|
|
74
74
|
htmlFor="file-uploader"
|
|
75
75
|
className={cn(
|
|
76
|
-
'group p-2 rounded-full hover:bg-ring/10 transition-all duration-200 ease-
|
|
76
|
+
'group p-2 rounded-full hover:bg-ring/10 transition-all duration-200 ease-out cursor-pointer',
|
|
77
77
|
labelProps?.className
|
|
78
78
|
)}
|
|
79
79
|
>
|
|
@@ -81,7 +81,7 @@ function FilePickerInputBase(
|
|
|
81
81
|
<Paperclip
|
|
82
82
|
{...iconProps}
|
|
83
83
|
className={cn(
|
|
84
|
-
'text-foreground group-hover:scale-110 transition-all duration-200 ease-
|
|
84
|
+
'text-foreground group-hover:scale-110 transition-all duration-200 ease-out',
|
|
85
85
|
iconProps?.className
|
|
86
86
|
)}
|
|
87
87
|
/>
|
|
@@ -12,7 +12,11 @@ function PlaceholderTextBase({ title, titles = [], rowProps, ...props }, ref) {
|
|
|
12
12
|
className={cn('absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2', props?.className)}
|
|
13
13
|
>
|
|
14
14
|
{rows.map((row, index) => (
|
|
15
|
-
<div
|
|
15
|
+
<div
|
|
16
|
+
key={`placeholder-text-${index}`}
|
|
17
|
+
{...rowProps}
|
|
18
|
+
className={cn('text-center text-muted-foreground', rowProps?.className)}
|
|
19
|
+
>
|
|
16
20
|
{row}
|
|
17
21
|
</div>
|
|
18
22
|
))}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { cn } from './utils';
|
|
3
|
+
|
|
4
|
+
function QuickActionsBase(
|
|
5
|
+
{
|
|
6
|
+
title,
|
|
7
|
+
description,
|
|
8
|
+
actions = [],
|
|
9
|
+
onAction = () => {},
|
|
10
|
+
containerProps,
|
|
11
|
+
titleProps,
|
|
12
|
+
descriptionProps,
|
|
13
|
+
actionProps,
|
|
14
|
+
...props
|
|
15
|
+
},
|
|
16
|
+
ref
|
|
17
|
+
) {
|
|
18
|
+
if (!actions.length) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
ref={ref}
|
|
25
|
+
{...containerProps}
|
|
26
|
+
className={cn('flex flex-col h-full overflow-y-auto py-2', containerProps?.className)}
|
|
27
|
+
>
|
|
28
|
+
<div className="grow" />
|
|
29
|
+
<div {...props} className={cn('grid gap-2 m-4', props?.className)}>
|
|
30
|
+
{title && (
|
|
31
|
+
<h2 {...titleProps} className={cn('font-medium text-foreground text-lg', titleProps?.className)}>
|
|
32
|
+
{title}
|
|
33
|
+
</h2>
|
|
34
|
+
)}
|
|
35
|
+
{description && (
|
|
36
|
+
<span {...descriptionProps} className={cn('text-sm text-muted-foreground mb-4', descriptionProps?.className)}>
|
|
37
|
+
{description}
|
|
38
|
+
</span>
|
|
39
|
+
)}
|
|
40
|
+
{actions.map((action, index) => (
|
|
41
|
+
<div
|
|
42
|
+
key={index}
|
|
43
|
+
{...actionProps}
|
|
44
|
+
className={cn(
|
|
45
|
+
'w-full border p-2 rounded-md wrap-break-word cursor-pointer overflow-hidden text-ellipsis bg-ring/5 hover:bg-ring/20 transition-colors duration-200 ease-out',
|
|
46
|
+
actionProps?.className
|
|
47
|
+
)}
|
|
48
|
+
onClick={() => onAction(action)}
|
|
49
|
+
>
|
|
50
|
+
{action}
|
|
51
|
+
</div>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const QuickActions = forwardRef(QuickActionsBase);
|
|
59
|
+
|
|
60
|
+
QuickActions.displayName = 'QuickActions';
|
|
61
|
+
|
|
62
|
+
export { QuickActions };
|
|
@@ -58,7 +58,7 @@ function SearchBase(
|
|
|
58
58
|
onClick={handleOnCancel}
|
|
59
59
|
{...cancelIconProps}
|
|
60
60
|
className={cn(
|
|
61
|
-
'absolute top-1/2 right-2 transform -translate-y-1/2 size-5 text-muted-foreground cursor-pointer hover:text-ring transition-all duration-300 ease-
|
|
61
|
+
'absolute top-1/2 right-2 transform -translate-y-1/2 size-5 text-muted-foreground cursor-pointer hover:text-ring transition-all duration-300 ease-out',
|
|
62
62
|
value.length > 0 ? 'opacity-100 scale-100' : 'opacity-0 scale-75 pointer-events-none',
|
|
63
63
|
cancelIconProps?.className
|
|
64
64
|
)}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Phone, PhoneIncoming, PhoneMissed, PhoneOutgoing } from 'lucide-react';
|
|
2
|
+
import { cn } from './utils';
|
|
3
|
+
|
|
4
|
+
const StatusCall = ({ fromMe, status, ...props }) => {
|
|
5
|
+
const CallIcon =
|
|
6
|
+
status === 'hungUp' ? Phone : fromMe ? PhoneOutgoing : status === 'reject' ? PhoneIncoming : PhoneMissed;
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<CallIcon
|
|
10
|
+
{...props}
|
|
11
|
+
className={cn('size-4', status === 'hungUp' ? 'text-green-500' : 'text-red-500', props?.className)}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
StatusCall.displayName = 'StatusCall';
|
|
17
|
+
|
|
18
|
+
export { StatusCall };
|
|
@@ -9,7 +9,7 @@ function StreamViewBase({ id, stream, mirror, className, muted, ...props }, ref)
|
|
|
9
9
|
const defaultClassName = 'size-full object-contain';
|
|
10
10
|
const mirrorClassName = mirror ? 'scale-x-[-1]' : '';
|
|
11
11
|
|
|
12
|
-
useImperativeHandle(ref, () => innerRef.current);
|
|
12
|
+
useImperativeHandle(ref, () => innerRef.current || {}, []);
|
|
13
13
|
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
if (innerRef.current && stream) {
|
|
@@ -116,13 +116,13 @@ function FullscreenStreamViewBase(
|
|
|
116
116
|
|
|
117
117
|
useImperativeHandle(
|
|
118
118
|
ref,
|
|
119
|
-
() =>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
() => ({
|
|
120
|
+
...(innerRef.current || {}),
|
|
121
|
+
isFullscreen,
|
|
122
|
+
isPictureInPicture,
|
|
123
|
+
toggleFullscreen,
|
|
124
|
+
togglePictureInPicture,
|
|
125
|
+
}),
|
|
126
126
|
[isFullscreen, isPictureInPicture, toggleFullscreen, togglePictureInPicture]
|
|
127
127
|
);
|
|
128
128
|
|
package/gen/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { AlertDialog } from './components/alert-dialog';
|
|
2
|
+
|
|
1
3
|
export {
|
|
2
4
|
Attachment,
|
|
3
5
|
AttachmentLink,
|
|
@@ -14,12 +16,18 @@ export { Badge } from './components/badge';
|
|
|
14
16
|
|
|
15
17
|
export { Button } from './components/button';
|
|
16
18
|
|
|
17
|
-
export {
|
|
19
|
+
export { ChatBubbleMessage, ChatBubbleInfo } from './components/chat-bubble';
|
|
20
|
+
|
|
21
|
+
export { ChatInput } from './components/chat-input';
|
|
22
|
+
|
|
23
|
+
export { ChatList } from './components/chat-list';
|
|
18
24
|
|
|
19
|
-
export {
|
|
25
|
+
export { Checkbox } from './components/checkbox';
|
|
20
26
|
|
|
21
27
|
export { DialogItem } from './components/dialog-item';
|
|
22
28
|
|
|
29
|
+
export { DialogsList } from './components/dialogs-list';
|
|
30
|
+
|
|
23
31
|
export { DismissLayer } from './components/dismiss-layer';
|
|
24
32
|
|
|
25
33
|
export { FilePickerInput, FilePickerDropzone } from './components/file-picker';
|
|
@@ -38,10 +46,14 @@ export { PlaceholderText } from './components/placeholder-text';
|
|
|
38
46
|
|
|
39
47
|
export { Presence, PresenceBadge } from './components/presence';
|
|
40
48
|
|
|
49
|
+
export { QuickActions } from './components/quick-actions';
|
|
50
|
+
|
|
41
51
|
export { Search } from './components/search';
|
|
42
52
|
|
|
43
53
|
export { Spinner } from './components/spinner';
|
|
44
54
|
|
|
55
|
+
export { StatusCall } from './components/status-call';
|
|
56
|
+
|
|
45
57
|
export { StatusIndicator } from './components/status-indicator';
|
|
46
58
|
|
|
47
59
|
export { StatusSent } from './components/status-sent';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@connectycube/react-ui-kit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "Simple React UI Kit generator with TSX/JSX",
|
|
5
5
|
"homepage": "https://github.com/ConnectyCube/react-ui-kit#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
|
66
66
|
"@radix-ui/react-avatar": "^1.1.11",
|
|
67
|
+
"@radix-ui/react-checkbox": "^1.3.3",
|
|
67
68
|
"@radix-ui/react-label": "^2.1.8",
|
|
68
69
|
"@radix-ui/react-slot": "^1.2.4",
|
|
69
70
|
"@radix-ui/react-switch": "^1.2.6",
|
|
@@ -72,9 +73,11 @@
|
|
|
72
73
|
"clsx": "^2.1.1",
|
|
73
74
|
"date-fns": "^4.1.0",
|
|
74
75
|
"linkify-react": "^4.3.2",
|
|
75
|
-
"lucide-react": "^0.
|
|
76
|
+
"lucide-react": "^0.562.0",
|
|
76
77
|
"react-intersection-observer": "^10.0.0",
|
|
77
|
-
"
|
|
78
|
+
"react-textarea-autosize": "^8.5.9",
|
|
79
|
+
"tailwind-merge": "^3.4.0",
|
|
80
|
+
"virtua": "^0.48.2"
|
|
78
81
|
},
|
|
79
82
|
"peerDependencies": {
|
|
80
83
|
"react": ">=18",
|
|
@@ -89,22 +92,22 @@
|
|
|
89
92
|
"@rollup/plugin-terser": "^0.4.4",
|
|
90
93
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
91
94
|
"@stylistic/eslint-plugin": "^5.6.1",
|
|
92
|
-
"@types/node": "^25.0.
|
|
95
|
+
"@types/node": "^25.0.3",
|
|
93
96
|
"@types/react": "^19.2.7",
|
|
94
97
|
"eslint": "^9.39.2",
|
|
95
98
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
96
|
-
"eslint-plugin-react-refresh": "^0.4.
|
|
99
|
+
"eslint-plugin-react-refresh": "^0.4.26",
|
|
97
100
|
"execa": "^9.6.1",
|
|
98
101
|
"fast-glob": "^3.3.3",
|
|
99
|
-
"fs-extra": "^11.3.
|
|
102
|
+
"fs-extra": "^11.3.3",
|
|
100
103
|
"globals": "^16.5.0",
|
|
101
104
|
"prettier": "^3.7.4",
|
|
102
105
|
"prompts": "^2.4.2",
|
|
103
|
-
"rollup": "^4.53.
|
|
106
|
+
"rollup": "^4.53.5",
|
|
104
107
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
105
108
|
"tslib": "^2.8.1",
|
|
106
109
|
"typescript": "^5.9.3",
|
|
107
|
-
"typescript-eslint": "^8.
|
|
110
|
+
"typescript-eslint": "^8.50.0"
|
|
108
111
|
},
|
|
109
112
|
"engines": {
|
|
110
113
|
"node": ">=18"
|