@connectycube/react-ui-kit 0.0.17 → 0.0.19
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 +27 -0
- package/configs/imports.json +9 -0
- package/dist/index.cjs +14 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/types/components/attachment.d.ts +45 -0
- package/dist/types/components/attachment.d.ts.map +1 -0
- package/dist/types/components/avatar.d.ts +3 -3
- package/dist/types/components/avatar.d.ts.map +1 -1
- package/dist/types/components/badge.d.ts +3 -3
- package/dist/types/components/badge.d.ts.map +1 -1
- package/dist/types/components/button.d.ts +2 -2
- package/dist/types/components/dialog-item.d.ts +46 -0
- package/dist/types/components/dialog-item.d.ts.map +1 -0
- package/dist/types/components/file-picker.d.ts +22 -0
- package/dist/types/components/file-picker.d.ts.map +1 -0
- package/dist/types/components/formatted-date.d.ts +8 -0
- package/dist/types/components/formatted-date.d.ts.map +1 -0
- package/dist/types/components/input.d.ts.map +1 -1
- package/dist/types/components/label.d.ts +5 -0
- package/dist/types/components/label.d.ts.map +1 -0
- package/dist/types/components/link-preview.d.ts +21 -0
- package/dist/types/components/link-preview.d.ts.map +1 -0
- package/dist/types/components/linkify-text.d.ts +9 -0
- package/dist/types/components/linkify-text.d.ts.map +1 -0
- package/dist/types/components/spinner.d.ts +3 -1
- package/dist/types/components/spinner.d.ts.map +1 -1
- package/dist/types/components/status-sent.d.ts +7 -0
- package/dist/types/components/status-sent.d.ts.map +1 -0
- package/dist/types/components/switch.d.ts +6 -0
- package/dist/types/components/switch.d.ts.map +1 -0
- package/dist/types/components/utils.d.ts +0 -1
- package/dist/types/components/utils.d.ts.map +1 -1
- package/dist/types/index.d.ts +21 -4
- package/dist/types/index.d.ts.map +1 -1
- package/gen/components/attachment.jsx +214 -0
- package/gen/components/avatar.jsx +13 -3
- package/gen/components/badge.jsx +3 -3
- package/gen/components/button.jsx +2 -2
- package/gen/components/dialog-item.jsx +149 -0
- package/gen/components/dismiss-layer.jsx +1 -1
- package/gen/components/file-picker.jsx +200 -0
- package/gen/components/formatted-date.jsx +57 -0
- package/gen/components/label.jsx +22 -0
- package/gen/components/link-preview.jsx +131 -0
- package/gen/components/linkify-text.jsx +31 -0
- package/gen/components/spinner.jsx +29 -5
- package/gen/components/status-sent.jsx +21 -0
- package/gen/components/switch.jsx +25 -0
- package/gen/components/utils.js +0 -7
- package/gen/index.js +46 -0
- package/package.json +5 -1
- package/src/components/attachment.tsx +270 -0
- package/src/components/avatar.tsx +16 -6
- package/src/components/badge.tsx +6 -6
- package/src/components/button.tsx +2 -2
- package/src/components/connectycube-ui/avatar.jsx +54 -0
- package/src/components/connectycube-ui/avatar.tsx +77 -0
- package/src/components/connectycube-ui/badge.jsx +45 -0
- package/src/components/connectycube-ui/badge.tsx +42 -0
- package/src/components/connectycube-ui/dialog-item.jsx +149 -0
- package/src/components/connectycube-ui/dialog-item.tsx +188 -0
- package/src/components/connectycube-ui/file-picker.jsx +200 -0
- package/src/components/connectycube-ui/file-picker.tsx +231 -0
- package/src/components/connectycube-ui/formatted-date.jsx +57 -0
- package/src/components/connectycube-ui/formatted-date.tsx +57 -0
- package/src/components/connectycube-ui/label.jsx +22 -0
- package/src/components/connectycube-ui/label.tsx +23 -0
- package/src/components/connectycube-ui/linkify-text.tsx +40 -0
- package/src/components/connectycube-ui/presence.jsx +81 -0
- package/src/components/connectycube-ui/presence.tsx +96 -0
- package/src/components/connectycube-ui/status-sent.jsx +21 -0
- package/src/components/connectycube-ui/status-sent.tsx +25 -0
- package/src/components/connectycube-ui/utils.js +10 -0
- package/src/components/connectycube-ui/utils.ts +10 -0
- package/src/components/dialog-item.tsx +188 -0
- package/src/components/dismiss-layer.tsx +1 -1
- package/src/components/file-picker.tsx +231 -0
- package/src/components/formatted-date.tsx +57 -0
- package/src/components/input.tsx +1 -1
- package/src/components/label.tsx +23 -0
- package/src/components/link-preview.tsx +149 -0
- package/src/components/linkify-text.tsx +41 -0
- package/src/components/spinner.tsx +31 -5
- package/src/components/status-sent.tsx +25 -0
- package/src/components/switch.tsx +27 -0
- package/src/components/utils.ts +0 -7
- package/src/index.ts +72 -4
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import * as SwitchPrimitive from '@radix-ui/react-switch';
|
|
5
|
+
import { cn } from './utils';
|
|
6
|
+
|
|
7
|
+
function Switch(props) {
|
|
8
|
+
return (
|
|
9
|
+
<SwitchPrimitive.Root
|
|
10
|
+
{...props}
|
|
11
|
+
className={cn(
|
|
12
|
+
'peer data-[state=checked]:bg-ring data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15em] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring disabled:cursor-not-allowed disabled:opacity-50',
|
|
13
|
+
props?.className
|
|
14
|
+
)}
|
|
15
|
+
>
|
|
16
|
+
<SwitchPrimitive.Thumb
|
|
17
|
+
className={cn(
|
|
18
|
+
'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0'
|
|
19
|
+
)}
|
|
20
|
+
/>
|
|
21
|
+
</SwitchPrimitive.Root>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { Switch };
|
package/gen/components/utils.js
CHANGED
|
@@ -9,13 +9,6 @@ export function getRandomString(length = 8) {
|
|
|
9
9
|
return (Date.now() / Math.random()).toString(36).replace('.', '').slice(0, length);
|
|
10
10
|
}
|
|
11
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
12
|
export function capitalize(str) {
|
|
20
13
|
return typeof str === 'string' && str.length > 0 ? `${str[0]?.toUpperCase()}${str.slice(1)}` : '';
|
|
21
14
|
}
|
package/gen/index.js
CHANGED
|
@@ -1,3 +1,49 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Attachment,
|
|
3
|
+
AttachmentLink,
|
|
4
|
+
AttachmentImage,
|
|
5
|
+
AttachmentAudio,
|
|
6
|
+
AttachmentVideo,
|
|
7
|
+
AttachmentFile,
|
|
8
|
+
AttachmentFailed,
|
|
9
|
+
} from './components/attachment';
|
|
10
|
+
|
|
11
|
+
export { Avatar } from './components/avatar';
|
|
12
|
+
|
|
13
|
+
export { Badge } from './components/badge';
|
|
14
|
+
|
|
15
|
+
export { Button } from './components/button';
|
|
16
|
+
|
|
17
|
+
export { DialogItem } from './components/dialog-item';
|
|
18
|
+
|
|
1
19
|
export { DismissLayer } from './components/dismiss-layer';
|
|
2
20
|
|
|
21
|
+
export { FilePickerInput, FilePickerDropzone } from './components/file-picker';
|
|
22
|
+
|
|
23
|
+
export { FormattedDate } from './components/formatted-date';
|
|
24
|
+
|
|
25
|
+
export { Input } from './components/input';
|
|
26
|
+
|
|
27
|
+
export { Label } from './components/label';
|
|
28
|
+
|
|
29
|
+
export { LinkPreview } from './components/link-preview';
|
|
30
|
+
|
|
31
|
+
export { LinkifyText } from './components/linkify-text';
|
|
32
|
+
|
|
33
|
+
export { PlaceholderText } from './components/placeholder-text';
|
|
34
|
+
|
|
35
|
+
export { Presence, PresenceBadge } from './components/presence';
|
|
36
|
+
|
|
37
|
+
export { Search } from './components/search';
|
|
38
|
+
|
|
39
|
+
export { Spinner } from './components/spinner';
|
|
40
|
+
|
|
41
|
+
export { StatusIndicator } from './components/status-indicator';
|
|
42
|
+
|
|
43
|
+
export { StatusSent } from './components/status-sent';
|
|
44
|
+
|
|
3
45
|
export { StreamView, LocalStreamView, RemoteStreamView, FullscreenStreamView } from './components/stream-view';
|
|
46
|
+
|
|
47
|
+
export { Switch } from './components/switch';
|
|
48
|
+
|
|
49
|
+
export * from './components/utils';
|
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.19",
|
|
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,12 +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",
|
|
69
|
+
"@radix-ui/react-switch": "^1.2.6",
|
|
68
70
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
69
71
|
"class-variance-authority": "^0.7.1",
|
|
70
72
|
"clsx": "^2.1.1",
|
|
73
|
+
"date-fns": "^4.1.0",
|
|
71
74
|
"execa": "^9.6.0",
|
|
72
75
|
"fs-extra": "^11.3.2",
|
|
76
|
+
"linkify-react": "^4.3.2",
|
|
73
77
|
"lucide-react": "^0.555.0",
|
|
74
78
|
"prompts": "^2.4.2",
|
|
75
79
|
"tailwind-merge": "^3.4.0"
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import { forwardRef, memo, useImperativeHandle, useRef, useState } from 'react';
|
|
3
|
+
import { File, FileXCorner, type LucideProps } from 'lucide-react';
|
|
4
|
+
import { Spinner } from './spinner';
|
|
5
|
+
import { cn, getRandomString } from './utils';
|
|
6
|
+
|
|
7
|
+
interface AttachmentProps {
|
|
8
|
+
uid?: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
mimeType?: string;
|
|
11
|
+
uploading?: boolean;
|
|
12
|
+
onReady?: (skipOnce?: boolean) => void;
|
|
13
|
+
linkProps?: AttachmentLinkProps;
|
|
14
|
+
containerProps?: React.ComponentProps<'div'>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface AttachmentLinkProps
|
|
18
|
+
extends React.ComponentProps<'a'>,
|
|
19
|
+
Omit<AttachmentProps, 'containerProps' & 'mimeType' & 'onReady'> {
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface AttachmentImageProps
|
|
24
|
+
extends React.ComponentProps<'img'>,
|
|
25
|
+
Omit<AttachmentProps, 'containerProps' & 'mimeType'> {}
|
|
26
|
+
|
|
27
|
+
interface AttachmentAudioProps
|
|
28
|
+
extends React.ComponentProps<'audio'>,
|
|
29
|
+
Omit<AttachmentProps, 'linkProps' & 'mimeType' & 'onReady'> {}
|
|
30
|
+
|
|
31
|
+
interface AttachmentVideoProps extends React.ComponentProps<'video'>, Omit<AttachmentProps, 'linkProps' & 'mimeType'> {
|
|
32
|
+
maxSize?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface AttachmentFileProps extends LucideProps, Omit<AttachmentProps, 'containerProps' & 'mimeType'> {
|
|
36
|
+
name?: string | undefined;
|
|
37
|
+
iconElement?: React.ReactNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface AttachmentFailedProps extends LucideProps, Omit<AttachmentProps, 'linkProps' & 'mimeType'> {
|
|
41
|
+
name?: string | undefined;
|
|
42
|
+
iconElement?: React.ReactNode;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function AttachmentLinkBase(
|
|
46
|
+
{ url, uploading = false, children, ...props }: AttachmentLinkProps,
|
|
47
|
+
ref: React.ForwardedRef<HTMLAnchorElement>
|
|
48
|
+
) {
|
|
49
|
+
return (
|
|
50
|
+
<a
|
|
51
|
+
ref={ref}
|
|
52
|
+
target="_blank"
|
|
53
|
+
rel="noopener noreferrer"
|
|
54
|
+
{...props}
|
|
55
|
+
href={url}
|
|
56
|
+
className={cn(
|
|
57
|
+
'group relative min-h-12 min-w-12 w-full flex items-center justify-center rounded-md overflow-hidden bg-ring/10 hover:bg-ring/20 transition-color duration-300 ease-out cursor-pointer',
|
|
58
|
+
props?.className
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
{children}
|
|
62
|
+
<Spinner loading={uploading} layout="overlay" />
|
|
63
|
+
</a>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const AttachmentLink = forwardRef<HTMLAnchorElement, AttachmentLinkProps>(AttachmentLinkBase);
|
|
68
|
+
|
|
69
|
+
AttachmentLink.displayName = 'AttachmentLink';
|
|
70
|
+
|
|
71
|
+
function AttachmentAudioBase(
|
|
72
|
+
{ uid, url, uploading = false, containerProps, ...props }: AttachmentAudioProps,
|
|
73
|
+
ref: React.ForwardedRef<HTMLAudioElement>
|
|
74
|
+
) {
|
|
75
|
+
const audioId = `attachment_audio_${uid || getRandomString()}`;
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div
|
|
79
|
+
{...containerProps}
|
|
80
|
+
className={cn('relative min-h-12 min-w-12 w-full rounded-md overflow-hidden', containerProps?.className)}
|
|
81
|
+
>
|
|
82
|
+
<audio ref={ref} src={url} id={audioId} controls {...props} />
|
|
83
|
+
<Spinner loading={uploading} layout="overlay" />
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const AttachmentAudio = forwardRef<HTMLAudioElement, AttachmentAudioProps>(AttachmentAudioBase);
|
|
89
|
+
|
|
90
|
+
function AttachmentVideoBase(
|
|
91
|
+
{ uid, url, maxSize = 360, uploading = false, onReady = () => {}, containerProps, ...props }: AttachmentVideoProps,
|
|
92
|
+
ref: React.ForwardedRef<HTMLVideoElement>
|
|
93
|
+
) {
|
|
94
|
+
const videoId = `attachment_video_${uid || getRandomString()}`;
|
|
95
|
+
const videoMaxSize = `${maxSize}px`;
|
|
96
|
+
const playerRef = useRef<HTMLVideoElement>(null);
|
|
97
|
+
const [style, setStyle] = useState<React.CSSProperties>({
|
|
98
|
+
maxHeight: videoMaxSize,
|
|
99
|
+
maxWidth: videoMaxSize,
|
|
100
|
+
});
|
|
101
|
+
const handleCanPlay = (event: React.SyntheticEvent<HTMLVideoElement, Event>) => {
|
|
102
|
+
const player = playerRef.current;
|
|
103
|
+
|
|
104
|
+
if (player) {
|
|
105
|
+
const { videoWidth, videoHeight } = player;
|
|
106
|
+
const ratio = videoWidth / videoHeight || 1;
|
|
107
|
+
const height = ratio < 1 ? videoMaxSize : `${Math.round(maxSize / ratio)}px`;
|
|
108
|
+
const width = ratio < 1 ? `${Math.round(maxSize * ratio)}px` : videoMaxSize;
|
|
109
|
+
|
|
110
|
+
setStyle({ height, width, maxHeight: height, maxWidth: width });
|
|
111
|
+
props.onCanPlay?.(event);
|
|
112
|
+
onReady();
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
useImperativeHandle(ref, () => playerRef.current!, []);
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div
|
|
120
|
+
{...containerProps}
|
|
121
|
+
className={cn('relative min-h-20 w-full rounded-md overflow-hidden', containerProps?.className)}
|
|
122
|
+
>
|
|
123
|
+
<video
|
|
124
|
+
id={videoId}
|
|
125
|
+
ref={playerRef}
|
|
126
|
+
src={url}
|
|
127
|
+
controls
|
|
128
|
+
preload="metadata"
|
|
129
|
+
style={style}
|
|
130
|
+
{...props}
|
|
131
|
+
onCanPlay={handleCanPlay}
|
|
132
|
+
className={cn('size-full', props?.className)}
|
|
133
|
+
/>
|
|
134
|
+
<Spinner loading={uploading} layout="overlay" />
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const AttachmentVideo = forwardRef<HTMLVideoElement, AttachmentVideoProps>(AttachmentVideoBase);
|
|
140
|
+
|
|
141
|
+
AttachmentVideo.displayName = 'AttachmentVideo';
|
|
142
|
+
|
|
143
|
+
function AttachmentImageBase(
|
|
144
|
+
{ uid, url, uploading = false, onReady = () => {}, linkProps, ...props }: AttachmentImageProps,
|
|
145
|
+
ref: React.ForwardedRef<HTMLImageElement>
|
|
146
|
+
) {
|
|
147
|
+
const imageId = `attachment_image_${uid || getRandomString()}`;
|
|
148
|
+
const handleLoad = (event: React.SyntheticEvent<HTMLImageElement, Event>) => {
|
|
149
|
+
props.onLoad?.(event);
|
|
150
|
+
onReady();
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<AttachmentLink href={url} uploading={uploading} {...linkProps}>
|
|
155
|
+
<img
|
|
156
|
+
ref={ref}
|
|
157
|
+
src={url}
|
|
158
|
+
id={imageId}
|
|
159
|
+
alt="attachment"
|
|
160
|
+
{...props}
|
|
161
|
+
className={cn(
|
|
162
|
+
'rounded-md object-cover min-h-12 min-w-12 max-h-[360px] group-hover:scale-103 transition-transform duration-300 ease-out',
|
|
163
|
+
props?.className
|
|
164
|
+
)}
|
|
165
|
+
onLoad={handleLoad}
|
|
166
|
+
/>
|
|
167
|
+
</AttachmentLink>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const AttachmentImage = forwardRef<HTMLImageElement, AttachmentImageProps>(AttachmentImageBase);
|
|
172
|
+
|
|
173
|
+
AttachmentImage.displayName = 'AttachmentImage';
|
|
174
|
+
|
|
175
|
+
function AttachmentFile({ url, name, uploading = false, iconElement, linkProps, ...props }: AttachmentFileProps) {
|
|
176
|
+
const fileId = `attachment_file_${props.id || getRandomString()}`;
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<AttachmentLink
|
|
180
|
+
href={url}
|
|
181
|
+
uploading={uploading}
|
|
182
|
+
{...linkProps}
|
|
183
|
+
className={cn('flex-row gap-2 px-2', linkProps?.className)}
|
|
184
|
+
>
|
|
185
|
+
{iconElement || (
|
|
186
|
+
<File
|
|
187
|
+
id={fileId}
|
|
188
|
+
{...props}
|
|
189
|
+
className={cn(
|
|
190
|
+
'size-6 shrink-0 text-foreground/85 group-hover:text-foreground duration-300 ease-out',
|
|
191
|
+
props?.className
|
|
192
|
+
)}
|
|
193
|
+
/>
|
|
194
|
+
)}
|
|
195
|
+
{name && (
|
|
196
|
+
<span className="font-medium line-clamp-1 break-all text-foreground/85 group-hover:text-foreground duration-300 ease-out">
|
|
197
|
+
{name}
|
|
198
|
+
</span>
|
|
199
|
+
)}
|
|
200
|
+
</AttachmentLink>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
AttachmentFile.displayName = 'AttachmentFile';
|
|
205
|
+
|
|
206
|
+
function AttachmentFailed({
|
|
207
|
+
name = 'Unknown file',
|
|
208
|
+
uploading = false,
|
|
209
|
+
iconElement,
|
|
210
|
+
containerProps,
|
|
211
|
+
...props
|
|
212
|
+
}: AttachmentFailedProps) {
|
|
213
|
+
const failedId = `attachment_failed_${props.id || getRandomString()}`;
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<div
|
|
217
|
+
{...containerProps}
|
|
218
|
+
className={cn(
|
|
219
|
+
'relative min-h-12 min-w-12 w-full flex flex-row items-center justify-center gap-2 px-2 bg-red-600/10 rounded-md overflow-hidden',
|
|
220
|
+
containerProps?.className
|
|
221
|
+
)}
|
|
222
|
+
>
|
|
223
|
+
{iconElement || (
|
|
224
|
+
<FileXCorner id={failedId} {...props} className={cn('size-6 shrink-0 text-red-600', props?.className)} />
|
|
225
|
+
)}
|
|
226
|
+
<span className="font-medium line-clamp-1 break-all text-red-600">{name}</span>
|
|
227
|
+
<Spinner loading={uploading} layout="overlay" />
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
AttachmentFailed.displayName = 'AttachmentFailed';
|
|
233
|
+
|
|
234
|
+
function AttachmentBase({ mimeType, ...props }: AttachmentProps) {
|
|
235
|
+
const [type = ''] = mimeType?.split('/') || [];
|
|
236
|
+
|
|
237
|
+
if (!props.url) {
|
|
238
|
+
return <AttachmentFailed {...props} />;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
switch (type) {
|
|
242
|
+
case 'image':
|
|
243
|
+
return <AttachmentImage {...props} />;
|
|
244
|
+
case 'video':
|
|
245
|
+
return <AttachmentVideo {...props} />;
|
|
246
|
+
case 'audio':
|
|
247
|
+
return <AttachmentAudio {...props} />;
|
|
248
|
+
default:
|
|
249
|
+
return <AttachmentFile name={mimeType} {...props} />;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const Attachment = memo(AttachmentBase);
|
|
254
|
+
|
|
255
|
+
export {
|
|
256
|
+
Attachment,
|
|
257
|
+
AttachmentLink,
|
|
258
|
+
AttachmentImage,
|
|
259
|
+
AttachmentAudio,
|
|
260
|
+
AttachmentVideo,
|
|
261
|
+
AttachmentFile,
|
|
262
|
+
AttachmentFailed,
|
|
263
|
+
type AttachmentLinkProps,
|
|
264
|
+
type AttachmentImageProps,
|
|
265
|
+
type AttachmentAudioProps,
|
|
266
|
+
type AttachmentVideoProps,
|
|
267
|
+
type AttachmentFileProps,
|
|
268
|
+
type AttachmentFailedProps,
|
|
269
|
+
type AttachmentProps,
|
|
270
|
+
};
|
|
@@ -2,12 +2,12 @@ import type React from 'react';
|
|
|
2
2
|
import { memo, forwardRef } from 'react';
|
|
3
3
|
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
4
4
|
import { PresenceBadge, type PresenceStatus, type PresenceBadgeProps } from './presence';
|
|
5
|
-
import { cn
|
|
5
|
+
import { cn } from './utils';
|
|
6
6
|
|
|
7
7
|
interface AvatarProps extends AvatarPrimitive.AvatarProps {
|
|
8
|
-
src?: string;
|
|
9
|
-
name?: string;
|
|
10
|
-
online?: boolean;
|
|
8
|
+
src?: string | undefined;
|
|
9
|
+
name?: string | undefined;
|
|
10
|
+
online?: boolean | undefined;
|
|
11
11
|
presence?: PresenceStatus;
|
|
12
12
|
onlineProps?: React.ComponentProps<'div'>;
|
|
13
13
|
presenceProps?: PresenceBadgeProps;
|
|
@@ -15,6 +15,13 @@ interface AvatarProps extends AvatarPrimitive.AvatarProps {
|
|
|
15
15
|
fallbackProps?: AvatarPrimitive.AvatarFallbackProps;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function getInitialsFromName(name?: string): string {
|
|
19
|
+
const words = name?.trim().split(/\s+/).filter(Boolean) ?? [];
|
|
20
|
+
const result = words.length > 1 ? `${words[0]?.[0]}${words[1]?.[0]}` : (words[0]?.slice(0, 2) ?? 'NA');
|
|
21
|
+
|
|
22
|
+
return result.toUpperCase();
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
function AvatarBase(
|
|
19
26
|
{
|
|
20
27
|
src,
|
|
@@ -48,13 +55,16 @@ function AvatarBase(
|
|
|
48
55
|
{online && (
|
|
49
56
|
<div
|
|
50
57
|
{...onlineProps}
|
|
51
|
-
className={cn(
|
|
58
|
+
className={cn(
|
|
59
|
+
'absolute top-0 right-0 rounded-full border-2 bg-green-600 border-green-200 size-3.5',
|
|
60
|
+
onlineProps?.className
|
|
61
|
+
)}
|
|
52
62
|
/>
|
|
53
63
|
)}
|
|
54
64
|
<PresenceBadge
|
|
55
65
|
status={presence}
|
|
56
66
|
{...presenceProps}
|
|
57
|
-
className={cn('absolute
|
|
67
|
+
className={cn('absolute bottom-0 right-0', presenceProps?.className)}
|
|
58
68
|
/>
|
|
59
69
|
</AvatarPrimitive.Root>
|
|
60
70
|
);
|
package/src/components/badge.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
|
-
import
|
|
3
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
4
4
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
5
5
|
import { cn } from './utils';
|
|
6
6
|
|
|
7
|
-
interface BadgeProps extends React.HTMLAttributes<
|
|
7
|
+
interface BadgeProps extends React.HTMLAttributes<HTMLElement>, VariantProps<typeof badgeVariants> {
|
|
8
8
|
asChild?: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -28,14 +28,14 @@ const badgeVariants = cva(
|
|
|
28
28
|
|
|
29
29
|
function BadgeBase(
|
|
30
30
|
{ className, variant, asChild = false, ...props }: BadgeProps,
|
|
31
|
-
ref?: React.ForwardedRef<
|
|
31
|
+
ref?: React.ForwardedRef<HTMLElement>
|
|
32
32
|
) {
|
|
33
|
-
const Comp = asChild ?
|
|
33
|
+
const Comp = asChild ? Slot : 'span';
|
|
34
34
|
|
|
35
|
-
return <Comp ref={ref} className={cn(badgeVariants({ variant }), className)}
|
|
35
|
+
return <Comp ref={ref} {...props} className={cn(badgeVariants({ variant }), className)} />;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const Badge = forwardRef<
|
|
38
|
+
const Badge = forwardRef<HTMLElement, BadgeProps>(BadgeBase);
|
|
39
39
|
|
|
40
40
|
Badge.displayName = 'Badge';
|
|
41
41
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
|
-
import
|
|
3
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
4
4
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
5
5
|
import { cn } from './utils';
|
|
6
6
|
|
|
@@ -40,7 +40,7 @@ function ButtonBase(
|
|
|
40
40
|
{ asChild = false, variant, size, className, ...props }: ButtonProps,
|
|
41
41
|
ref?: React.ForwardedRef<HTMLButtonElement>
|
|
42
42
|
) {
|
|
43
|
-
const Comp = asChild ?
|
|
43
|
+
const Comp = asChild ? Slot : 'button';
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
46
|
<Comp
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { memo, forwardRef } from 'react';
|
|
2
|
+
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
3
|
+
import { PresenceBadge } from './presence';
|
|
4
|
+
import { cn } from './utils';
|
|
5
|
+
|
|
6
|
+
function getInitialsFromName(name) {
|
|
7
|
+
const words = name?.trim().split(/\s+/).filter(Boolean) ?? [];
|
|
8
|
+
const result = words.length > 1 ? `${words[0]?.[0]}${words[1]?.[0]}` : (words[0]?.slice(0, 2) ?? 'NA');
|
|
9
|
+
|
|
10
|
+
return result.toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function AvatarBase(
|
|
14
|
+
{ src, name = 'NA', online, presence, className, onlineProps, presenceProps, imageProps, fallbackProps, ...props },
|
|
15
|
+
ref
|
|
16
|
+
) {
|
|
17
|
+
const initials = getInitialsFromName(name);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<AvatarPrimitive.Root ref={ref} {...props} className={cn('relative flex size-8 shrink-0 rounded-full', className)}>
|
|
21
|
+
<AvatarPrimitive.Image
|
|
22
|
+
{...imageProps}
|
|
23
|
+
src={src}
|
|
24
|
+
className={cn('aspect-square size-full rounded-full overflow-hidden object-cover', imageProps?.className)}
|
|
25
|
+
/>
|
|
26
|
+
<AvatarPrimitive.Fallback
|
|
27
|
+
{...fallbackProps}
|
|
28
|
+
className={cn('bg-muted size-full rounded-full flex items-center justify-center', fallbackProps?.className)}
|
|
29
|
+
>
|
|
30
|
+
{initials}
|
|
31
|
+
</AvatarPrimitive.Fallback>
|
|
32
|
+
{online && (
|
|
33
|
+
<div
|
|
34
|
+
{...onlineProps}
|
|
35
|
+
className={cn(
|
|
36
|
+
'absolute top-0 right-0 rounded-full border-2 bg-green-600 border-green-200 size-3.5',
|
|
37
|
+
onlineProps?.className
|
|
38
|
+
)}
|
|
39
|
+
/>
|
|
40
|
+
)}
|
|
41
|
+
<PresenceBadge
|
|
42
|
+
status={presence}
|
|
43
|
+
{...presenceProps}
|
|
44
|
+
className={cn('absolute bottom-0 right-0', presenceProps?.className)}
|
|
45
|
+
/>
|
|
46
|
+
</AvatarPrimitive.Root>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const Avatar = memo(forwardRef(AvatarBase));
|
|
51
|
+
|
|
52
|
+
Avatar.displayName = 'Avatar';
|
|
53
|
+
|
|
54
|
+
export { Avatar };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import { memo, forwardRef } from 'react';
|
|
3
|
+
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
4
|
+
import { PresenceBadge, type PresenceStatus, type PresenceBadgeProps } from './presence';
|
|
5
|
+
import { cn } from './utils';
|
|
6
|
+
|
|
7
|
+
interface AvatarProps extends AvatarPrimitive.AvatarProps {
|
|
8
|
+
src?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
online?: boolean;
|
|
11
|
+
presence?: PresenceStatus;
|
|
12
|
+
onlineProps?: React.ComponentProps<'div'>;
|
|
13
|
+
presenceProps?: PresenceBadgeProps;
|
|
14
|
+
imageProps?: AvatarPrimitive.AvatarImageProps;
|
|
15
|
+
fallbackProps?: AvatarPrimitive.AvatarFallbackProps;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getInitialsFromName(name?: string): string {
|
|
19
|
+
const words = name?.trim().split(/\s+/).filter(Boolean) ?? [];
|
|
20
|
+
const result = words.length > 1 ? `${words[0]?.[0]}${words[1]?.[0]}` : (words[0]?.slice(0, 2) ?? 'NA');
|
|
21
|
+
|
|
22
|
+
return result.toUpperCase();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function AvatarBase(
|
|
26
|
+
{
|
|
27
|
+
src,
|
|
28
|
+
name = 'NA',
|
|
29
|
+
online,
|
|
30
|
+
presence,
|
|
31
|
+
className,
|
|
32
|
+
onlineProps,
|
|
33
|
+
presenceProps,
|
|
34
|
+
imageProps,
|
|
35
|
+
fallbackProps,
|
|
36
|
+
...props
|
|
37
|
+
}: AvatarProps,
|
|
38
|
+
ref: React.ForwardedRef<HTMLDivElement>
|
|
39
|
+
) {
|
|
40
|
+
const initials = getInitialsFromName(name);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<AvatarPrimitive.Root ref={ref} {...props} className={cn('relative flex size-8 shrink-0 rounded-full', className)}>
|
|
44
|
+
<AvatarPrimitive.Image
|
|
45
|
+
{...imageProps}
|
|
46
|
+
src={src}
|
|
47
|
+
className={cn('aspect-square size-full rounded-full overflow-hidden object-cover', imageProps?.className)}
|
|
48
|
+
/>
|
|
49
|
+
<AvatarPrimitive.Fallback
|
|
50
|
+
{...fallbackProps}
|
|
51
|
+
className={cn('bg-muted size-full rounded-full flex items-center justify-center', fallbackProps?.className)}
|
|
52
|
+
>
|
|
53
|
+
{initials}
|
|
54
|
+
</AvatarPrimitive.Fallback>
|
|
55
|
+
{online && (
|
|
56
|
+
<div
|
|
57
|
+
{...onlineProps}
|
|
58
|
+
className={cn(
|
|
59
|
+
'absolute top-0 right-0 rounded-full border-2 bg-green-600 border-green-200 size-3.5',
|
|
60
|
+
onlineProps?.className
|
|
61
|
+
)}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
<PresenceBadge
|
|
65
|
+
status={presence}
|
|
66
|
+
{...presenceProps}
|
|
67
|
+
className={cn('absolute bottom-0 right-0', presenceProps?.className)}
|
|
68
|
+
/>
|
|
69
|
+
</AvatarPrimitive.Root>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const Avatar = memo(forwardRef<HTMLDivElement, AvatarProps>(AvatarBase));
|
|
74
|
+
|
|
75
|
+
Avatar.displayName = 'Avatar';
|
|
76
|
+
|
|
77
|
+
export { Avatar, type AvatarProps };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { cn } from './utils';
|
|
5
|
+
|
|
6
|
+
const badgeVariants = cva(
|
|
7
|
+
'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
|
|
12
|
+
secondary: 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
|
|
13
|
+
destructive:
|
|
14
|
+
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
|
15
|
+
outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
variant: 'default',
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
function BadgeBase({ className, variant, asChild = false, ...props }, ref) {
|
|
25
|
+
const Comp = asChild ? Slot : 'span';
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Comp
|
|
29
|
+
ref={ref}
|
|
30
|
+
{...props}
|
|
31
|
+
className={cn(
|
|
32
|
+
badgeVariants({
|
|
33
|
+
variant,
|
|
34
|
+
}),
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const Badge = forwardRef(BadgeBase);
|
|
42
|
+
|
|
43
|
+
Badge.displayName = 'Badge';
|
|
44
|
+
|
|
45
|
+
export { Badge };
|