@connectycube/react-ui-kit 0.0.16 → 0.0.18

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 (94) hide show
  1. package/configs/dependencies.json +32 -3
  2. package/configs/imports.json +13 -2
  3. package/dist/index.cjs +4 -4
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +4 -4
  6. package/dist/index.js.map +1 -1
  7. package/dist/types/components/alert-dialog.d.ts.map +1 -1
  8. package/dist/types/components/avatar.d.ts +2 -2
  9. package/dist/types/components/avatar.d.ts.map +1 -1
  10. package/dist/types/components/badge.d.ts +11 -0
  11. package/dist/types/components/badge.d.ts.map +1 -0
  12. package/dist/types/components/button.d.ts +3 -5
  13. package/dist/types/components/button.d.ts.map +1 -1
  14. package/dist/types/components/dialog-item.d.ts +46 -0
  15. package/dist/types/components/dialog-item.d.ts.map +1 -0
  16. package/dist/types/components/file-picker.d.ts +22 -0
  17. package/dist/types/components/file-picker.d.ts.map +1 -0
  18. package/dist/types/components/formatted-date.d.ts +8 -0
  19. package/dist/types/components/formatted-date.d.ts.map +1 -0
  20. package/dist/types/components/input.d.ts +5 -0
  21. package/dist/types/components/input.d.ts.map +1 -0
  22. package/dist/types/components/label.d.ts +5 -0
  23. package/dist/types/components/label.d.ts.map +1 -0
  24. package/dist/types/components/link-preview.d.ts +21 -0
  25. package/dist/types/components/link-preview.d.ts.map +1 -0
  26. package/dist/types/components/linkify-text.d.ts +9 -0
  27. package/dist/types/components/linkify-text.d.ts.map +1 -0
  28. package/dist/types/components/search.d.ts +15 -0
  29. package/dist/types/components/search.d.ts.map +1 -0
  30. package/dist/types/components/spinner.d.ts +12 -0
  31. package/dist/types/components/spinner.d.ts.map +1 -0
  32. package/dist/types/components/status-sent.d.ts +7 -0
  33. package/dist/types/components/status-sent.d.ts.map +1 -0
  34. package/dist/types/components/stream-view.d.ts.map +1 -1
  35. package/dist/types/components/utils.d.ts +0 -2
  36. package/dist/types/components/utils.d.ts.map +1 -1
  37. package/gen/components/alert-dialog.jsx +3 -1
  38. package/gen/components/avatar.jsx +13 -3
  39. package/gen/components/badge.jsx +45 -0
  40. package/gen/components/button.jsx +6 -9
  41. package/gen/components/dialog-item.jsx +149 -0
  42. package/gen/components/file-picker.jsx +200 -0
  43. package/gen/components/formatted-date.jsx +57 -0
  44. package/gen/components/input.jsx +23 -0
  45. package/gen/components/label.jsx +22 -0
  46. package/gen/components/link-preview.jsx +131 -0
  47. package/gen/components/linkify-text.jsx +31 -0
  48. package/gen/components/search.jsx +75 -0
  49. package/gen/components/spinner.jsx +36 -0
  50. package/gen/components/status-sent.jsx +21 -0
  51. package/gen/components/stream-view.jsx +5 -1
  52. package/gen/components/utils.js +0 -11
  53. package/package.json +5 -2
  54. package/src/components/alert-dialog.tsx +3 -1
  55. package/src/components/avatar.tsx +16 -6
  56. package/src/components/badge.tsx +42 -0
  57. package/src/components/button.tsx +12 -14
  58. package/src/components/connectycube-ui/avatar.jsx +54 -0
  59. package/src/components/connectycube-ui/avatar.tsx +77 -0
  60. package/src/components/connectycube-ui/badge.jsx +45 -0
  61. package/src/components/connectycube-ui/badge.tsx +42 -0
  62. package/src/components/connectycube-ui/dialog-item.jsx +149 -0
  63. package/src/components/connectycube-ui/dialog-item.tsx +188 -0
  64. package/src/components/connectycube-ui/file-picker.jsx +200 -0
  65. package/src/components/connectycube-ui/file-picker.tsx +231 -0
  66. package/src/components/connectycube-ui/formatted-date.jsx +57 -0
  67. package/src/components/connectycube-ui/formatted-date.tsx +57 -0
  68. package/src/components/connectycube-ui/label.jsx +22 -0
  69. package/src/components/connectycube-ui/label.tsx +23 -0
  70. package/src/components/connectycube-ui/linkify-text.tsx +40 -0
  71. package/src/components/connectycube-ui/presence.jsx +81 -0
  72. package/src/components/connectycube-ui/presence.tsx +96 -0
  73. package/src/components/connectycube-ui/status-sent.jsx +21 -0
  74. package/src/components/connectycube-ui/status-sent.tsx +25 -0
  75. package/src/components/connectycube-ui/utils.js +10 -0
  76. package/src/components/connectycube-ui/utils.ts +10 -0
  77. package/src/components/dialog-item.tsx +188 -0
  78. package/src/components/file-picker.tsx +231 -0
  79. package/src/components/formatted-date.tsx +57 -0
  80. package/src/components/input.tsx +26 -0
  81. package/src/components/label.tsx +23 -0
  82. package/src/components/link-preview.tsx +149 -0
  83. package/src/components/linkify-text.tsx +41 -0
  84. package/src/components/placeholder-text.tsx +1 -1
  85. package/src/components/search.tsx +86 -0
  86. package/src/components/spinner.tsx +42 -0
  87. package/src/components/status-sent.tsx +25 -0
  88. package/src/components/stream-view.tsx +9 -5
  89. package/src/components/utils.ts +0 -11
  90. package/dist/tsconfig.tsbuildinfo +0 -1
  91. package/dist/types/components/animated-loader.d.ts +0 -10
  92. package/dist/types/components/animated-loader.d.ts.map +0 -1
  93. package/gen/components/animated-loader.jsx +0 -12
  94. package/src/components/animated-loader.tsx +0 -16
@@ -0,0 +1,200 @@
1
+ import { forwardRef, useState } from 'react';
2
+ import { FilePlusCorner, Paperclip } from 'lucide-react';
3
+ import { Label } from './label';
4
+ import { cn } from './utils';
5
+
6
+ const areValidFiles = (files, accept) => {
7
+ if (files.length === 0) {
8
+ return false;
9
+ }
10
+
11
+ const acceptList =
12
+ typeof accept === 'string' ? accept.split(/\s*,\s*|\s+/).map((type) => type.toLowerCase().trim()) : null;
13
+
14
+ if (acceptList === null || acceptList.length === 0) {
15
+ return false;
16
+ }
17
+
18
+ return files.every((file) => {
19
+ const { type, size } = file;
20
+
21
+ if (typeof type !== 'string' || typeof size !== 'number' || size <= 0 || type.length === 0) {
22
+ return false;
23
+ }
24
+
25
+ const fileMimeType = type.toLowerCase();
26
+ const [fileType, fileSubtype] = fileMimeType.split('/');
27
+
28
+ return acceptList.some(
29
+ (acceptType) =>
30
+ acceptType.startsWith('*') ||
31
+ acceptType === fileMimeType ||
32
+ (fileType && acceptType.endsWith('/*') && acceptType.startsWith(fileType)) ||
33
+ (acceptType.startsWith('.') && acceptType.slice(1) === fileSubtype)
34
+ );
35
+ });
36
+ };
37
+ const handleFiles = (fileList, accept, multiple, onSelectFile, onInvalidFile) => {
38
+ const files = Array.from(fileList || []).filter(Boolean);
39
+
40
+ if (files.length === 0) {
41
+ return;
42
+ }
43
+
44
+ const validated = areValidFiles(files, accept);
45
+
46
+ if (validated) {
47
+ onSelectFile(multiple ? files : files[0] ? [files[0]] : []);
48
+ } else {
49
+ onInvalidFile();
50
+ }
51
+ };
52
+
53
+ function FilePickerInputBase(
54
+ {
55
+ onSelectFile = () => {},
56
+ onInvalidFile = () => {},
57
+ multiple = false,
58
+ accept = '*/*',
59
+ iconElement,
60
+ labelProps,
61
+ iconProps,
62
+ ...props
63
+ },
64
+ ref
65
+ ) {
66
+ const handleChange = (event) => {
67
+ handleFiles(event.currentTarget.files, accept, multiple, onSelectFile, onInvalidFile);
68
+ event.currentTarget.value = '';
69
+ };
70
+
71
+ return (
72
+ <Label
73
+ {...labelProps}
74
+ htmlFor="file-uploader"
75
+ className={cn(
76
+ 'group p-2 rounded-full hover:bg-ring/10 transition-all duration-200 ease-in-out cursor-pointer',
77
+ labelProps?.className
78
+ )}
79
+ >
80
+ {iconElement || (
81
+ <Paperclip
82
+ {...iconProps}
83
+ className={cn(
84
+ 'text-foreground group-hover:scale-110 transition-all duration-200 ease-in-out',
85
+ iconProps?.className
86
+ )}
87
+ />
88
+ )}
89
+ <input
90
+ ref={ref}
91
+ id="file-uploader"
92
+ type="file"
93
+ multiple={multiple}
94
+ accept={accept}
95
+ onChange={handleChange}
96
+ {...props}
97
+ className={cn('hidden', props?.className)}
98
+ />
99
+ </Label>
100
+ );
101
+ }
102
+
103
+ const FilePickerInput = forwardRef(FilePickerInputBase);
104
+
105
+ FilePickerInput.displayName = 'FilePickerInput';
106
+
107
+ function FilePickerDropzoneBase(
108
+ {
109
+ onSelectFile = () => {},
110
+ onInvalidFile = () => {},
111
+ multiple = false,
112
+ accept = '*/*',
113
+ children,
114
+ iconElement,
115
+ placeholder,
116
+ dropZoneProps,
117
+ placeholderContainerProps,
118
+ iconProps,
119
+ placeholderProps,
120
+ ...props
121
+ },
122
+ ref
123
+ ) {
124
+ const [isDragging, setIsDragging] = useState(false);
125
+ const handleDragEvent = (event) => {
126
+ event.preventDefault();
127
+ event.stopPropagation();
128
+ };
129
+ const handleDrop = (event) => {
130
+ handleDragEvent(event);
131
+ setIsDragging(false);
132
+ handleFiles(event.dataTransfer.files, accept, multiple, onSelectFile, onInvalidFile);
133
+ event.dataTransfer.clearData();
134
+ };
135
+ const handleDragEnter = (event) => {
136
+ handleDragEvent(event);
137
+
138
+ if (event.dataTransfer.items && event.dataTransfer.items.length > 0) {
139
+ setIsDragging(true);
140
+ }
141
+ };
142
+ const handleDragLeave = (event) => {
143
+ if (!event.currentTarget.contains(event.relatedTarget)) {
144
+ setIsDragging(false);
145
+ }
146
+ };
147
+
148
+ return (
149
+ <div
150
+ ref={ref}
151
+ onDragEnter={handleDragEnter}
152
+ onDragLeave={handleDragLeave}
153
+ onDragOver={handleDragEvent}
154
+ {...props}
155
+ className={cn('size-full relative', props?.className)}
156
+ >
157
+ {children}
158
+ <div
159
+ onDrop={handleDrop}
160
+ {...dropZoneProps}
161
+ className={cn(
162
+ 'group absolute top-0 left-0 size-full',
163
+ 'flex items-center justify-center',
164
+ 'transition-all duration-300 ease-out',
165
+ 'border-2 border-dashed border-ring bg-ring/25 rounded-md',
166
+ isDragging
167
+ ? 'opacity-100 pointer-events-auto visible scale-100'
168
+ : 'opacity-0 pointer-events-none invisible scale-98',
169
+ dropZoneProps?.className
170
+ )}
171
+ >
172
+ <div
173
+ {...placeholderContainerProps}
174
+ className={cn(
175
+ 'bg-muted flex flex-row items-center gap-2 rounded-full py-2 px-4 transition-all',
176
+ isDragging ? 'scale-100' : 'scale-90',
177
+ placeholderContainerProps?.className
178
+ )}
179
+ >
180
+ {iconElement || (
181
+ <FilePlusCorner
182
+ absoluteStrokeWidth
183
+ {...iconProps}
184
+ className={cn('text-ring size-6', iconProps?.className)}
185
+ />
186
+ )}
187
+ <span {...placeholderProps} className={cn('text-ring text-md font-bold', placeholderProps?.className)}>
188
+ {placeholder || 'Drop files here'}
189
+ </span>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ );
194
+ }
195
+
196
+ const FilePickerDropzone = forwardRef(FilePickerDropzoneBase);
197
+
198
+ FilePickerDropzone.displayName = 'FilePickerDropzone';
199
+
200
+ export { FilePickerInput, FilePickerDropzone, FilePickerInput as Input, FilePickerDropzone as Dropzone };
@@ -0,0 +1,57 @@
1
+ import { forwardRef, memo } from 'react';
2
+ import { differenceInCalendarDays, format, formatDistanceToNow, isToday } from 'date-fns';
3
+ import { el, enUS, uk } from 'date-fns/locale';
4
+ import { cn } from './utils';
5
+
6
+ const locales = {
7
+ en: enUS,
8
+ el: el,
9
+ ua: uk,
10
+ };
11
+
12
+ function formatDate(date, language = 'en', distanceToNow = false) {
13
+ const locale = locales[language] ?? enUS;
14
+
15
+ if (distanceToNow) {
16
+ return formatDistanceToNow(date, {
17
+ locale,
18
+ addSuffix: true,
19
+ });
20
+ }
21
+
22
+ const isWithinLast7Days = (date) => {
23
+ const diff = differenceInCalendarDays(new Date(), date);
24
+
25
+ return diff >= 0 && diff <= 6;
26
+ };
27
+
28
+ return isToday(date)
29
+ ? format(date, 'p', {
30
+ locale,
31
+ })
32
+ : isWithinLast7Days(date)
33
+ ? format(date, 'eeee', {
34
+ locale,
35
+ })
36
+ : format(date, 'P', {
37
+ locale,
38
+ });
39
+ }
40
+
41
+ function FormattedDateBase({ date, language, distanceToNow, ...props }, ref) {
42
+ if (!date) {
43
+ return null;
44
+ }
45
+
46
+ return (
47
+ <span ref={ref} {...props} className={cn('text-xs text-muted-foreground', props?.className)}>
48
+ {formatDate(date, language, distanceToNow)}
49
+ </span>
50
+ );
51
+ }
52
+
53
+ const FormattedDate = memo(forwardRef(FormattedDateBase));
54
+
55
+ FormattedDate.displayName = 'FormattedDate';
56
+
57
+ export { FormattedDate };
@@ -0,0 +1,23 @@
1
+ import { forwardRef } from 'react';
2
+ import { cn } from './utils';
3
+
4
+ function InputBase(props, ref) {
5
+ return (
6
+ <input
7
+ ref={ref}
8
+ {...props}
9
+ className={cn(
10
+ 'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-10 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
11
+ 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring',
12
+ 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
13
+ props?.className
14
+ )}
15
+ />
16
+ );
17
+ }
18
+
19
+ const Input = forwardRef(InputBase);
20
+
21
+ Input.displayName = 'Input';
22
+
23
+ export { Input };
@@ -0,0 +1,22 @@
1
+ import { forwardRef } from 'react';
2
+ import { Root as LabelRoot } from '@radix-ui/react-label';
3
+ import { cn } from './utils';
4
+
5
+ function LabelBase({ ...props }, ref) {
6
+ return (
7
+ <LabelRoot
8
+ ref={ref}
9
+ {...props}
10
+ className={cn(
11
+ 'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
12
+ props?.className
13
+ )}
14
+ />
15
+ );
16
+ }
17
+
18
+ const Label = forwardRef(LabelBase);
19
+
20
+ Label.displayName = 'Label';
21
+
22
+ export { Label };
@@ -0,0 +1,131 @@
1
+ import { forwardRef, memo, useCallback, useState } from 'react';
2
+ import { Globe } from 'lucide-react';
3
+ import { cn } from './utils';
4
+
5
+ const decodeHtmlEntities = (text) => {
6
+ if (text.length === 0) {
7
+ return text;
8
+ }
9
+
10
+ const element = document.createElement('div');
11
+
12
+ element.innerHTML = text;
13
+
14
+ return element.textContent.trim();
15
+ };
16
+
17
+ function LinkPreviewBase(
18
+ {
19
+ thin = false,
20
+ title = '',
21
+ description = '',
22
+ icon,
23
+ iconFallbackElement,
24
+ image,
25
+ onReady = () => {},
26
+ titleContainerProps,
27
+ iconProps,
28
+ iconFallbackProps,
29
+ titleProps,
30
+ descriptionProps,
31
+ imageContainerProps,
32
+ imageProps,
33
+ ...props
34
+ },
35
+ ref
36
+ ) {
37
+ const [iconSrc, setIconSrc] = useState(icon);
38
+ const [imageSrc, setImageSrc] = useState(image);
39
+ const handleOnLoad = useCallback(
40
+ (event) => {
41
+ imageProps?.onLoad?.(event);
42
+ onReady();
43
+ },
44
+ [onReady, imageProps]
45
+ );
46
+ const handleIconOnError = useCallback(
47
+ (event) => {
48
+ iconProps?.onError?.(event);
49
+ setIconSrc(undefined);
50
+ },
51
+ [iconProps]
52
+ );
53
+ const handleImageOnError = useCallback(
54
+ (event) => {
55
+ imageProps?.onError?.(event);
56
+ setImageSrc(undefined);
57
+ },
58
+ [imageProps]
59
+ );
60
+
61
+ return (
62
+ <a
63
+ ref={ref}
64
+ target="_blank"
65
+ rel="noopener noreferrer"
66
+ {...props}
67
+ className={cn(
68
+ 'transition-color duration-300 ease-out',
69
+ 'grid items-start gap-2 p-2 mt-1 overflow-hidden rounded border-l-4 border-l-ring bg-white hover:bg-ring/20',
70
+ thin ? 'grid-cols-3' : 'grid-cols-1',
71
+ props?.className
72
+ )}
73
+ >
74
+ <div
75
+ {...titleContainerProps}
76
+ className={cn('flex items-start gap-2', thin ? 'col-span-3' : 'col-span-1', titleContainerProps?.className)}
77
+ >
78
+ {iconSrc ? (
79
+ <img
80
+ alt="icon"
81
+ src={iconSrc}
82
+ {...iconProps}
83
+ onError={handleIconOnError}
84
+ className={cn('size-5', iconProps?.className)}
85
+ />
86
+ ) : (
87
+ iconFallbackElement || <Globe {...iconFallbackProps} className={cn('size-5', iconFallbackProps?.className)} />
88
+ )}
89
+ <span
90
+ {...titleProps}
91
+ className={cn('text-sm font-bold', thin ? 'line-clamp-1' : 'line-clamp-2', titleProps?.className)}
92
+ >
93
+ {decodeHtmlEntities(title)}
94
+ </span>
95
+ </div>
96
+ {description && (
97
+ <span
98
+ {...descriptionProps}
99
+ className={cn(
100
+ 'text-sm line-clamp-5',
101
+ thin ? (imageSrc ? 'col-span-2' : 'col-span-3') : 'col-span-1',
102
+ descriptionProps?.className
103
+ )}
104
+ >
105
+ {decodeHtmlEntities(description)}
106
+ </span>
107
+ )}
108
+ {imageSrc && (
109
+ <div
110
+ {...imageContainerProps}
111
+ className={cn('flex items-center justify-center', imageContainerProps?.className)}
112
+ >
113
+ <img
114
+ alt="banner"
115
+ src={imageSrc}
116
+ {...imageProps}
117
+ onLoad={handleOnLoad}
118
+ onError={handleImageOnError}
119
+ className={cn('rounded max-w-full object-contain', thin ? 'max-h-25' : 'max-h-60', imageProps?.className)}
120
+ />
121
+ </div>
122
+ )}
123
+ </a>
124
+ );
125
+ }
126
+
127
+ const LinkPreview = memo(forwardRef(LinkPreviewBase));
128
+
129
+ LinkPreview.displayName = 'LinkPreview';
130
+
131
+ export { LinkPreview };
@@ -0,0 +1,31 @@
1
+ import { forwardRef, memo, useMemo } from 'react';
2
+ import Linkify from 'linkify-react';
3
+ import { cn } from './utils';
4
+
5
+ const DEFAULT_LINKIFY_OPTIONS = {
6
+ target: '_blank',
7
+ rel: 'noopener noreferrer',
8
+ };
9
+
10
+ function LinkifyTextBase({ text, linkifyProps, ...props }, ref) {
11
+ const options = useMemo(
12
+ () => ({
13
+ ...DEFAULT_LINKIFY_OPTIONS,
14
+ ...linkifyProps,
15
+ className: cn('text-blue-500 hover:text-blue-600 hover:underline', linkifyProps?.className),
16
+ }),
17
+ [linkifyProps]
18
+ );
19
+
20
+ return (
21
+ <p ref={ref} {...props} className={cn('wrap-break-word text-base', props?.className)}>
22
+ <Linkify options={options}>{text}</Linkify>
23
+ </p>
24
+ );
25
+ }
26
+
27
+ const LinkifyText = memo(forwardRef(LinkifyTextBase));
28
+
29
+ LinkifyText.displayName = 'LinkifyText';
30
+
31
+ export { LinkifyText };
@@ -0,0 +1,75 @@
1
+ import { forwardRef, useState } from 'react';
2
+ import { cn } from './utils';
3
+ import { Input } from './input';
4
+ import { Search as SearchIcon, X as CloseIcon } from 'lucide-react';
5
+
6
+ function SearchBase(
7
+ {
8
+ onSearch = () => {},
9
+ onCancel = () => {},
10
+ hasSearchIcon = true,
11
+ hasCancelIcon = true,
12
+ searchIconProps,
13
+ cancelIconProps,
14
+ containerProps,
15
+ ...props
16
+ },
17
+ ref
18
+ ) {
19
+ const [value, setValue] = useState('');
20
+ const handleOnSearch = (e) => {
21
+ const keyword = e.target.value;
22
+
23
+ setValue(keyword);
24
+ onSearch(keyword.toLowerCase());
25
+ };
26
+ const handleOnCancel = () => {
27
+ setValue('');
28
+ onCancel();
29
+ };
30
+
31
+ return (
32
+ <div {...containerProps} className={cn('group relative', containerProps?.className)}>
33
+ {hasSearchIcon && (
34
+ <SearchIcon
35
+ {...searchIconProps}
36
+ className={cn(
37
+ 'absolute top-1/2 left-2 transform -translate-y-1/2 size-5 text-muted-foreground group-focus-within:text-ring',
38
+ searchIconProps?.className
39
+ )}
40
+ />
41
+ )}
42
+ <Input
43
+ ref={ref}
44
+ name="search"
45
+ placeholder={props?.placeholder || 'Search...'}
46
+ value={value}
47
+ onChange={handleOnSearch}
48
+ {...props}
49
+ className={cn(
50
+ 'placeholder:text-muted-foreground focus-visible:ring-ring/50 focus-visible:ring flex',
51
+ hasSearchIcon ? 'pl-10' : 'pl-3',
52
+ hasCancelIcon ? 'pr-10' : 'pr-3',
53
+ props?.className
54
+ )}
55
+ />
56
+ {hasCancelIcon && (
57
+ <CloseIcon
58
+ onClick={handleOnCancel}
59
+ {...cancelIconProps}
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-in-out',
62
+ value.length > 0 ? 'opacity-100 scale-100' : 'opacity-0 scale-75 pointer-events-none',
63
+ cancelIconProps?.className
64
+ )}
65
+ />
66
+ )}
67
+ </div>
68
+ );
69
+ }
70
+
71
+ const Search = forwardRef(SearchBase);
72
+
73
+ Search.displayName = 'Search';
74
+
75
+ export { Search };
@@ -0,0 +1,36 @@
1
+ import { Loader, LoaderCircle } from 'lucide-react';
2
+ import { cn } from './utils';
3
+
4
+ function Spinner({ loading = false, layout = 'flow', type = 'default', ...props }) {
5
+ const LoaderIcon = type === 'circle' ? LoaderCircle : Loader;
6
+
7
+ if (!loading) {
8
+ return null;
9
+ }
10
+
11
+ const spinnerElement = (
12
+ <LoaderIcon
13
+ strokeWidth={2.5}
14
+ {...props}
15
+ className={cn('animate-spin text-ring', layout === 'flow' && 'mx-auto', props?.className)}
16
+ />
17
+ );
18
+
19
+ switch (layout) {
20
+ case 'absolute':
21
+ return (
22
+ <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">{spinnerElement}</div>
23
+ );
24
+ case 'centered':
25
+ return <div className="flex items-center justify-center size-full">{spinnerElement}</div>;
26
+ case 'overlay':
27
+ return <div className="flex items-center justify-center size-full absolute bg-muted/50">{spinnerElement}</div>;
28
+ case 'flow':
29
+ default:
30
+ return spinnerElement;
31
+ }
32
+ }
33
+
34
+ Spinner.displayName = 'Spinner';
35
+
36
+ export { Spinner };
@@ -0,0 +1,21 @@
1
+ import { Ban, Check, CheckCheck, Clock } from 'lucide-react';
2
+ import { cn } from './utils';
3
+
4
+ const StatusSent = ({ status, ...props }) => {
5
+ switch (status) {
6
+ case 'wait':
7
+ return <Clock {...props} className={cn('text-gray-500 size-4', props?.className)} />;
8
+ case 'sent':
9
+ return <Check {...props} className={cn('text-gray-500 size-4', props?.className)} />;
10
+ case 'read':
11
+ return <CheckCheck {...props} className={cn('text-gray-500 size-4', props?.className)} />;
12
+ case 'lost':
13
+ return <Ban {...props} className={cn('text-red-500 size-4', props?.className)} />;
14
+ default:
15
+ return null;
16
+ }
17
+ };
18
+
19
+ StatusSent.displayName = 'StatusSent';
20
+
21
+ export { StatusSent };
@@ -1,6 +1,10 @@
1
1
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
2
2
  import { Maximize, Minimize, PictureInPicture2 } from 'lucide-react';
3
- import { cn, getRandomString } from './utils';
3
+ import { cn } from './utils';
4
+
5
+ function getRandomString(length = 8) {
6
+ return (Date.now() / Math.random()).toString(36).replace('.', '').slice(0, length);
7
+ }
4
8
 
5
9
  function StreamViewBase({ id, stream, mirror, className, muted, ...props }, ref) {
6
10
  const innerRef = useRef(null);
@@ -5,17 +5,6 @@ export function cn(...inputs) {
5
5
  return twMerge(clsx(inputs));
6
6
  }
7
7
 
8
- export function getRandomString(length = 8) {
9
- return (Date.now() / Math.random()).toString(36).replace('.', '').slice(0, length);
10
- }
11
-
12
- export function getInitialsFromName(name) {
13
- const words = name?.trim().split(/\s+/).filter(Boolean) ?? [];
14
- const result = words.length > 1 ? `${words[0]?.[0]}${words[1]?.[0]}` : (words[0]?.slice(0, 2) ?? 'NA');
15
-
16
- return result.toUpperCase();
17
- }
18
-
19
8
  export function capitalize(str) {
20
9
  return typeof str === 'string' && str.length > 0 ? `${str[0]?.toUpperCase()}${str.slice(1)}` : '';
21
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@connectycube/react-ui-kit",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
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,13 +64,16 @@
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-label": "^2.1.8",
67
68
  "@radix-ui/react-slot": "^1.2.4",
68
69
  "@radix-ui/react-tooltip": "^1.2.8",
69
70
  "class-variance-authority": "^0.7.1",
70
71
  "clsx": "^2.1.1",
72
+ "date-fns": "^4.1.0",
71
73
  "execa": "^9.6.0",
72
74
  "fs-extra": "^11.3.2",
73
- "lucide-react": "^0.554.0",
75
+ "linkify-react": "^4.3.2",
76
+ "lucide-react": "^0.555.0",
74
77
  "prompts": "^2.4.2",
75
78
  "tailwind-merge": "^3.4.0"
76
79
  },
@@ -96,7 +96,9 @@ function AlertDialogBase(
96
96
  className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', footerProps?.className)}
97
97
  >
98
98
  <AlertDialogPrimitive.Cancel {...cancelProps} onClick={onCancel}>
99
- <Button {...cancelButtonProps}>{cancelTitle}</Button>
99
+ <Button variant="outline" {...cancelButtonProps}>
100
+ {cancelTitle}
101
+ </Button>
100
102
  </AlertDialogPrimitive.Cancel>
101
103
  <AlertDialogPrimitive.Action {...actionProps} onClick={onConfirm}>
102
104
  <Button {...actionButtonProps}>{actionTitle}</Button>