@connectycube/react-ui-kit 0.0.15 → 0.0.17

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 (59) hide show
  1. package/configs/dependencies.json +17 -2
  2. package/configs/imports.json +9 -2
  3. package/dist/index.cjs +5 -5
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +9 -9
  6. package/dist/index.js.map +1 -1
  7. package/dist/types/components/alert-dialog.d.ts +28 -0
  8. package/dist/types/components/alert-dialog.d.ts.map +1 -0
  9. package/dist/types/components/avatar.d.ts +2 -1
  10. package/dist/types/components/avatar.d.ts.map +1 -1
  11. package/dist/types/components/badge.d.ts +11 -0
  12. package/dist/types/components/badge.d.ts.map +1 -0
  13. package/dist/types/components/button.d.ts +12 -0
  14. package/dist/types/components/button.d.ts.map +1 -0
  15. package/dist/types/components/dismiss-layer.d.ts +2 -1
  16. package/dist/types/components/dismiss-layer.d.ts.map +1 -1
  17. package/dist/types/components/input.d.ts +5 -0
  18. package/dist/types/components/input.d.ts.map +1 -0
  19. package/dist/types/components/placeholder-text.d.ts +2 -1
  20. package/dist/types/components/placeholder-text.d.ts.map +1 -1
  21. package/dist/types/components/presence.d.ts +9 -4
  22. package/dist/types/components/presence.d.ts.map +1 -1
  23. package/dist/types/components/search.d.ts +15 -0
  24. package/dist/types/components/search.d.ts.map +1 -0
  25. package/dist/types/components/spinner.d.ts +10 -0
  26. package/dist/types/components/spinner.d.ts.map +1 -0
  27. package/dist/types/components/status-indicator.d.ts +2 -1
  28. package/dist/types/components/status-indicator.d.ts.map +1 -1
  29. package/dist/types/components/stream-view.d.ts +5 -4
  30. package/dist/types/components/stream-view.d.ts.map +1 -1
  31. package/gen/components/alert-dialog.jsx +94 -0
  32. package/gen/components/avatar.jsx +2 -2
  33. package/gen/components/badge.jsx +45 -0
  34. package/gen/components/button.jsx +57 -0
  35. package/gen/components/dismiss-layer.jsx +2 -2
  36. package/gen/components/input.jsx +23 -0
  37. package/gen/components/placeholder-text.jsx +3 -2
  38. package/gen/components/presence.jsx +2 -7
  39. package/gen/components/search.jsx +75 -0
  40. package/gen/components/spinner.jsx +12 -0
  41. package/gen/components/status-indicator.jsx +2 -2
  42. package/package.json +5 -2
  43. package/src/components/alert-dialog.tsx +118 -0
  44. package/src/components/avatar.tsx +4 -3
  45. package/src/components/badge.tsx +42 -0
  46. package/src/components/button.tsx +58 -0
  47. package/src/components/dismiss-layer.tsx +4 -3
  48. package/src/components/input.tsx +26 -0
  49. package/src/components/placeholder-text.tsx +4 -3
  50. package/src/components/presence.tsx +3 -7
  51. package/src/components/search.tsx +86 -0
  52. package/src/components/spinner.tsx +16 -0
  53. package/src/components/status-indicator.tsx +4 -3
  54. package/src/components/stream-view.tsx +5 -4
  55. package/dist/tsconfig.tsbuildinfo +0 -1
  56. package/dist/types/components/animated-loader.d.ts +0 -10
  57. package/dist/types/components/animated-loader.d.ts.map +0 -1
  58. package/gen/components/animated-loader.jsx +0 -12
  59. package/src/components/animated-loader.tsx +0 -16
@@ -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,12 @@
1
+ import { LoaderCircle } from 'lucide-react';
2
+ import { cn } from './utils';
3
+
4
+ function Spinner({ loading = true, ...props }) {
5
+ return loading ? (
6
+ <LoaderCircle strokeWidth={2.5} {...props} className={cn('animate-spin mx-auto text-ring', props?.className)} />
7
+ ) : null;
8
+ }
9
+
10
+ Spinner.displayName = 'Spinner';
11
+
12
+ export { Spinner };
@@ -1,4 +1,4 @@
1
- import { forwardRef, memo } from 'react';
1
+ import { forwardRef } from 'react';
2
2
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
3
3
  import { cn } from './utils';
4
4
 
@@ -62,7 +62,7 @@ function StatusIndicatorBase(
62
62
  );
63
63
  }
64
64
 
65
- const StatusIndicator = memo(forwardRef(StatusIndicatorBase));
65
+ const StatusIndicator = forwardRef(StatusIndicatorBase);
66
66
 
67
67
  StatusIndicator.displayName = 'StatusIndicator';
68
68
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@connectycube/react-ui-kit",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
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": {
@@ -62,12 +62,15 @@
62
62
  "jsx"
63
63
  ],
64
64
  "dependencies": {
65
+ "@radix-ui/react-alert-dialog": "^1.1.15",
65
66
  "@radix-ui/react-avatar": "^1.1.11",
67
+ "@radix-ui/react-slot": "^1.2.4",
66
68
  "@radix-ui/react-tooltip": "^1.2.8",
69
+ "class-variance-authority": "^0.7.1",
67
70
  "clsx": "^2.1.1",
68
71
  "execa": "^9.6.0",
69
72
  "fs-extra": "^11.3.2",
70
- "lucide-react": "^0.554.0",
73
+ "lucide-react": "^0.555.0",
71
74
  "prompts": "^2.4.2",
72
75
  "tailwind-merge": "^3.4.0"
73
76
  },
@@ -0,0 +1,118 @@
1
+ import type React from 'react';
2
+ import { forwardRef } from 'react';
3
+ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
4
+ import { Button, type ButtonProps } from './button';
5
+ import { cn } from './utils';
6
+
7
+ interface AlertDialogProps extends AlertDialogPrimitive.AlertDialogProps {
8
+ title?: string;
9
+ description?: string;
10
+ cancelTitle?: string;
11
+ actionTitle?: string;
12
+ onCancel?: () => void;
13
+ onConfirm?: () => void;
14
+ rootProps?: AlertDialogPrimitive.AlertDialogProps;
15
+ triggerElement: React.ReactElement;
16
+ triggerProps?: AlertDialogPrimitive.AlertDialogTriggerProps;
17
+ portalProps?: AlertDialogPrimitive.AlertDialogPortalProps;
18
+ overlayProps?: AlertDialogPrimitive.AlertDialogOverlayProps;
19
+ contentProps?: AlertDialogPrimitive.AlertDialogContentProps;
20
+ headerProps?: React.ComponentProps<'div'>;
21
+ titleProps?: AlertDialogPrimitive.AlertDialogTitleProps;
22
+ descriptionProps?: AlertDialogPrimitive.AlertDialogDescriptionProps;
23
+ footerProps?: React.ComponentProps<'div'>;
24
+ cancelProps?: AlertDialogPrimitive.AlertDialogCancelProps;
25
+ cancelButtonProps?: ButtonProps;
26
+ actionProps?: AlertDialogPrimitive.AlertDialogActionProps;
27
+ actionButtonProps?: ButtonProps;
28
+ }
29
+
30
+ function AlertDialogBase(
31
+ {
32
+ title = '',
33
+ description = '',
34
+ cancelTitle = 'Cancel',
35
+ actionTitle = 'Confirm',
36
+ onCancel = () => {},
37
+ onConfirm = () => {},
38
+ rootProps,
39
+ triggerElement,
40
+ triggerProps,
41
+ portalProps,
42
+ overlayProps,
43
+ contentProps,
44
+ headerProps,
45
+ titleProps,
46
+ descriptionProps,
47
+ footerProps,
48
+ cancelProps,
49
+ cancelButtonProps,
50
+ actionProps,
51
+ actionButtonProps,
52
+ ...props
53
+ }: AlertDialogProps,
54
+ ref?: React.ForwardedRef<HTMLDivElement>
55
+ ) {
56
+ return (
57
+ <div ref={ref} {...props}>
58
+ <AlertDialogPrimitive.Root {...rootProps}>
59
+ <AlertDialogPrimitive.Trigger asChild {...triggerProps}>
60
+ {triggerElement}
61
+ </AlertDialogPrimitive.Trigger>
62
+ <AlertDialogPrimitive.Portal {...portalProps}>
63
+ <AlertDialogPrimitive.Overlay
64
+ {...overlayProps}
65
+ className={cn(
66
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
67
+ overlayProps?.className
68
+ )}
69
+ />
70
+ <AlertDialogPrimitive.Content
71
+ {...contentProps}
72
+ className={cn(
73
+ 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2em)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
74
+ contentProps?.className
75
+ )}
76
+ >
77
+ <div
78
+ {...headerProps}
79
+ className={cn('flex flex-col gap-2 text-center sm:text-left', headerProps?.className)}
80
+ >
81
+ <AlertDialogPrimitive.Title
82
+ {...titleProps}
83
+ className={cn('text-lg font-semibold', titleProps?.className)}
84
+ >
85
+ {title}
86
+ </AlertDialogPrimitive.Title>
87
+ <AlertDialogPrimitive.Description
88
+ {...descriptionProps}
89
+ className={cn('text-muted-foreground text-sm', descriptionProps?.className)}
90
+ >
91
+ {description}
92
+ </AlertDialogPrimitive.Description>
93
+ </div>
94
+ <div
95
+ {...footerProps}
96
+ className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', footerProps?.className)}
97
+ >
98
+ <AlertDialogPrimitive.Cancel {...cancelProps} onClick={onCancel}>
99
+ <Button variant="outline" {...cancelButtonProps}>
100
+ {cancelTitle}
101
+ </Button>
102
+ </AlertDialogPrimitive.Cancel>
103
+ <AlertDialogPrimitive.Action {...actionProps} onClick={onConfirm}>
104
+ <Button {...actionButtonProps}>{actionTitle}</Button>
105
+ </AlertDialogPrimitive.Action>
106
+ </div>
107
+ </AlertDialogPrimitive.Content>
108
+ </AlertDialogPrimitive.Portal>
109
+ </AlertDialogPrimitive.Root>
110
+ </div>
111
+ );
112
+ }
113
+
114
+ const AlertDialog = forwardRef<HTMLDivElement, AlertDialogProps>(AlertDialogBase);
115
+
116
+ AlertDialog.displayName = 'AlertDialog';
117
+
118
+ export { AlertDialog, type AlertDialogProps };
@@ -1,4 +1,5 @@
1
- import { forwardRef, memo } from 'react';
1
+ import type React from 'react';
2
+ import { memo, forwardRef } from 'react';
2
3
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
3
4
  import { PresenceBadge, type PresenceStatus, type PresenceBadgeProps } from './presence';
4
5
  import { cn, getInitialsFromName } from './utils';
@@ -27,7 +28,7 @@ function AvatarBase(
27
28
  fallbackProps,
28
29
  ...props
29
30
  }: AvatarProps,
30
- ref: React.Ref<HTMLDivElement>
31
+ ref: React.ForwardedRef<HTMLDivElement>
31
32
  ) {
32
33
  const initials = getInitialsFromName(name);
33
34
 
@@ -40,7 +41,7 @@ function AvatarBase(
40
41
  />
41
42
  <AvatarPrimitive.Fallback
42
43
  {...fallbackProps}
43
- className={cn('bg-muted flex size-full items-center justify-center', fallbackProps?.className)}
44
+ className={cn('bg-muted size-full rounded-full flex items-center justify-center', fallbackProps?.className)}
44
45
  >
45
46
  {initials}
46
47
  </AvatarPrimitive.Fallback>
@@ -0,0 +1,42 @@
1
+ import type React from 'react';
2
+ import { forwardRef } from 'react';
3
+ import * as SlotPrimitive from '@radix-ui/react-slot';
4
+ import { cva, type VariantProps } from 'class-variance-authority';
5
+ import { cn } from './utils';
6
+
7
+ interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
8
+ asChild?: boolean;
9
+ }
10
+
11
+ const badgeVariants = cva(
12
+ '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',
13
+ {
14
+ variants: {
15
+ variant: {
16
+ default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
17
+ secondary: 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
18
+ destructive:
19
+ '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',
20
+ outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: 'default',
25
+ },
26
+ }
27
+ );
28
+
29
+ function BadgeBase(
30
+ { className, variant, asChild = false, ...props }: BadgeProps,
31
+ ref?: React.ForwardedRef<HTMLSpanElement>
32
+ ) {
33
+ const Comp = asChild ? SlotPrimitive.Root : 'span';
34
+
35
+ return <Comp ref={ref} className={cn(badgeVariants({ variant }), className)} {...props} />;
36
+ }
37
+
38
+ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(BadgeBase);
39
+
40
+ Badge.displayName = 'Badge';
41
+
42
+ export { Badge, type BadgeProps };
@@ -0,0 +1,58 @@
1
+ import type React from 'react';
2
+ import { forwardRef } from 'react';
3
+ import * as SlotPrimitive from '@radix-ui/react-slot';
4
+ import { cva, type VariantProps } from 'class-variance-authority';
5
+ import { cn } from './utils';
6
+
7
+ interface ButtonProps extends React.ComponentProps<'button'>, VariantProps<typeof buttonVariants> {
8
+ asChild?: boolean;
9
+ }
10
+
11
+ const buttonVariants = cva(
12
+ 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*=size-])]:size-4 shrink-0 [&_svg]:shrink-0 outline-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',
13
+ {
14
+ variants: {
15
+ variant: {
16
+ default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
17
+ destructive:
18
+ 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
19
+ outline:
20
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
21
+ secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
22
+ ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
23
+ link: 'text-primary underline-offset-4 hover:underline',
24
+ },
25
+ size: {
26
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
27
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
28
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
29
+ icon: 'size-9',
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ variant: 'default',
34
+ size: 'default',
35
+ },
36
+ }
37
+ );
38
+
39
+ function ButtonBase(
40
+ { asChild = false, variant, size, className, ...props }: ButtonProps,
41
+ ref?: React.ForwardedRef<HTMLButtonElement>
42
+ ) {
43
+ const Comp = asChild ? SlotPrimitive.Root : 'button';
44
+
45
+ return (
46
+ <Comp
47
+ ref={ref}
48
+ {...props}
49
+ className={cn(buttonVariants({ variant, size, className }), 'transition-all ease-in-out duration-300')}
50
+ />
51
+ );
52
+ }
53
+
54
+ const Button = forwardRef<HTMLButtonElement, ButtonProps>(ButtonBase);
55
+
56
+ Button.displayName = 'Button';
57
+
58
+ export { Button, type ButtonProps };
@@ -1,4 +1,5 @@
1
- import { useCallback, useEffect, useRef, useImperativeHandle, memo, forwardRef } from 'react';
1
+ import type React from 'react';
2
+ import { useCallback, useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
2
3
  import { cn } from './utils';
3
4
 
4
5
  interface DismissLayerProps extends React.ComponentProps<'div'> {
@@ -11,7 +12,7 @@ interface DismissLayerProps extends React.ComponentProps<'div'> {
11
12
 
12
13
  function DismissLayerBase(
13
14
  { active, onDismiss, disableClickOutside = false, disableEscKeyPress = false, disabled, ...props }: DismissLayerProps,
14
- ref: React.Ref<HTMLDivElement>
15
+ ref: React.ForwardedRef<HTMLDivElement>
15
16
  ) {
16
17
  const innerRef = useRef<HTMLDivElement>(null);
17
18
 
@@ -58,7 +59,7 @@ function DismissLayerBase(
58
59
  );
59
60
  }
60
61
 
61
- const DismissLayer = memo(forwardRef<HTMLDivElement, DismissLayerProps>(DismissLayerBase));
62
+ const DismissLayer = forwardRef<HTMLDivElement, DismissLayerProps>(DismissLayerBase);
62
63
 
63
64
  DismissLayer.displayName = 'DismissLayer';
64
65
 
@@ -0,0 +1,26 @@
1
+ import type React from 'react';
2
+ import { forwardRef } from 'react';
3
+ import { cn } from './utils';
4
+
5
+ type InputProps = React.ComponentProps<'input'>;
6
+
7
+ function InputBase(props: InputProps, ref?: React.ForwardedRef<HTMLInputElement>) {
8
+ return (
9
+ <input
10
+ ref={ref}
11
+ {...props}
12
+ className={cn(
13
+ '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',
14
+ 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring',
15
+ 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
16
+ props?.className
17
+ )}
18
+ />
19
+ );
20
+ }
21
+
22
+ const Input = forwardRef(InputBase);
23
+
24
+ Input.displayName = 'Input';
25
+
26
+ export { Input, type InputProps };
@@ -1,4 +1,5 @@
1
- import { forwardRef, memo } from 'react';
1
+ import * as React from 'react';
2
+ import { forwardRef } from 'react';
2
3
  import { cn } from './utils';
3
4
 
4
5
  interface PlaceholderTextProps extends React.ComponentProps<'div'> {
@@ -9,7 +10,7 @@ interface PlaceholderTextProps extends React.ComponentProps<'div'> {
9
10
 
10
11
  function PlaceholderTextBase(
11
12
  { title, titles = [], rowProps, ...props }: PlaceholderTextProps,
12
- ref: React.Ref<HTMLDivElement>
13
+ ref: React.ForwardedRef<HTMLDivElement>
13
14
  ) {
14
15
  const rows = typeof title === 'string' ? [title, ...titles] : titles;
15
16
 
@@ -28,7 +29,7 @@ function PlaceholderTextBase(
28
29
  );
29
30
  }
30
31
 
31
- const PlaceholderText = memo(forwardRef(PlaceholderTextBase));
32
+ const PlaceholderText = forwardRef(PlaceholderTextBase);
32
33
 
33
34
  PlaceholderText.displayName = 'PlaceholderText';
34
35
 
@@ -1,4 +1,4 @@
1
- import { memo } from 'react';
1
+ import type React from 'react';
2
2
  import { capitalize, cn } from './utils';
3
3
 
4
4
  type PresenceStatus = 'available' | 'busy' | 'away' | 'unknown' | undefined;
@@ -15,7 +15,7 @@ interface PresenceBadgeProps extends React.ComponentProps<'svg'> {
15
15
  status?: PresenceStatus;
16
16
  }
17
17
 
18
- function PresenceBadgeBase({ status, ...props }: PresenceBadgeProps) {
18
+ function PresenceBadge({ status, ...props }: PresenceBadgeProps) {
19
19
  switch (status) {
20
20
  case 'available':
21
21
  return (
@@ -78,11 +78,9 @@ function PresenceBadgeBase({ status, ...props }: PresenceBadgeProps) {
78
78
  }
79
79
  }
80
80
 
81
- const PresenceBadge = memo(PresenceBadgeBase);
82
-
83
81
  PresenceBadge.displayName = 'PresenceBadge';
84
82
 
85
- function PresenceBase({ badge = true, status, label, badgeProps, labelProps, ...props }: PresenceProps) {
83
+ function Presence({ badge = true, status, label, badgeProps, labelProps, ...props }: PresenceProps) {
86
84
  const presence = capitalize(label || status);
87
85
 
88
86
  return (
@@ -93,8 +91,6 @@ function PresenceBase({ badge = true, status, label, badgeProps, labelProps, ...
93
91
  );
94
92
  }
95
93
 
96
- const Presence = memo(PresenceBase);
97
-
98
94
  Presence.displayName = 'Presence';
99
95
 
100
96
  export { Presence, PresenceBadge, type PresenceStatus, type PresenceProps, type PresenceBadgeProps };
@@ -0,0 +1,86 @@
1
+ import type React from 'react';
2
+ import { forwardRef, useState } from 'react';
3
+ import { cn } from './utils';
4
+ import { Input, type InputProps } from './input';
5
+ import { Search as SearchIcon, X as CloseIcon, type LucideProps } from 'lucide-react';
6
+
7
+ interface SearchProps extends InputProps {
8
+ onSearch?: (value: string) => void;
9
+ onCancel?: () => void;
10
+ hasSearchIcon?: boolean;
11
+ hasCancelIcon?: boolean;
12
+ searchIconProps?: LucideProps;
13
+ cancelIconProps?: LucideProps;
14
+ containerProps?: React.ComponentProps<'div'>;
15
+ }
16
+
17
+ function SearchBase(
18
+ {
19
+ onSearch = () => {},
20
+ onCancel = () => {},
21
+ hasSearchIcon = true,
22
+ hasCancelIcon = true,
23
+ searchIconProps,
24
+ cancelIconProps,
25
+ containerProps,
26
+ ...props
27
+ }: SearchProps,
28
+ ref?: React.ForwardedRef<HTMLInputElement>
29
+ ) {
30
+ const [value, setValue] = useState<string>('');
31
+ const handleOnSearch = (e: React.ChangeEvent<HTMLInputElement>): void => {
32
+ const keyword = e.target.value;
33
+
34
+ setValue(keyword);
35
+ onSearch(keyword.toLowerCase());
36
+ };
37
+ const handleOnCancel = (): void => {
38
+ setValue('');
39
+ onCancel();
40
+ };
41
+
42
+ return (
43
+ <div {...containerProps} className={cn('group relative', containerProps?.className)}>
44
+ {hasSearchIcon && (
45
+ <SearchIcon
46
+ {...searchIconProps}
47
+ className={cn(
48
+ 'absolute top-1/2 left-2 transform -translate-y-1/2 size-5 text-muted-foreground group-focus-within:text-ring',
49
+ searchIconProps?.className
50
+ )}
51
+ />
52
+ )}
53
+ <Input
54
+ ref={ref}
55
+ name="search"
56
+ placeholder={props?.placeholder || 'Search...'}
57
+ value={value}
58
+ onChange={handleOnSearch}
59
+ {...props}
60
+ className={cn(
61
+ 'placeholder:text-muted-foreground focus-visible:ring-ring/50 focus-visible:ring flex',
62
+ hasSearchIcon ? 'pl-10' : 'pl-3',
63
+ hasCancelIcon ? 'pr-10' : 'pr-3',
64
+ props?.className
65
+ )}
66
+ />
67
+ {hasCancelIcon && (
68
+ <CloseIcon
69
+ onClick={handleOnCancel}
70
+ {...cancelIconProps}
71
+ className={cn(
72
+ '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',
73
+ value.length > 0 ? 'opacity-100 scale-100' : 'opacity-0 scale-75 pointer-events-none',
74
+ cancelIconProps?.className
75
+ )}
76
+ />
77
+ )}
78
+ </div>
79
+ );
80
+ }
81
+
82
+ const Search = forwardRef<HTMLInputElement, SearchProps>(SearchBase);
83
+
84
+ Search.displayName = 'Search';
85
+
86
+ export { Search, type SearchProps };
@@ -0,0 +1,16 @@
1
+ import { LoaderCircle, type LucideProps } from 'lucide-react';
2
+ import { cn } from './utils';
3
+
4
+ interface SpinnerProps extends LucideProps {
5
+ loading?: boolean;
6
+ }
7
+
8
+ function Spinner({ loading = true, ...props }: SpinnerProps) {
9
+ return loading ? (
10
+ <LoaderCircle strokeWidth={2.5} {...props} className={cn('animate-spin mx-auto text-ring', props?.className)} />
11
+ ) : null;
12
+ }
13
+
14
+ Spinner.displayName = 'Spinner';
15
+
16
+ export { Spinner, type SpinnerProps };
@@ -1,4 +1,5 @@
1
- import { forwardRef, memo } from 'react';
1
+ import type React from 'react';
2
+ import { forwardRef } from 'react';
2
3
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
3
4
  import { cn } from './utils';
4
5
 
@@ -35,7 +36,7 @@ function StatusIndicatorBase(
35
36
  className,
36
37
  ...props
37
38
  }: StatusIndicatorProps,
38
- ref: React.Ref<HTMLDivElement>
39
+ ref: React.ForwardedRef<HTMLDivElement>
39
40
  ) {
40
41
  if (disabled) return null;
41
42
 
@@ -77,7 +78,7 @@ function StatusIndicatorBase(
77
78
  );
78
79
  }
79
80
 
80
- const StatusIndicator = memo(forwardRef(StatusIndicatorBase));
81
+ const StatusIndicator = forwardRef(StatusIndicatorBase);
81
82
 
82
83
  StatusIndicator.displayName = 'StatusIndicator';
83
84
 
@@ -1,3 +1,4 @@
1
+ import type React from 'react';
1
2
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
2
3
  import { Maximize, Minimize, PictureInPicture2, type LucideProps } from 'lucide-react';
3
4
  import { cn, getRandomString } from './utils';
@@ -9,7 +10,7 @@ interface StreamViewProps extends React.ComponentProps<'video'> {
9
10
 
10
11
  function StreamViewBase(
11
12
  { id, stream, mirror, className, muted, ...props }: StreamViewProps,
12
- ref: React.Ref<HTMLVideoElement>
13
+ ref: React.ForwardedRef<HTMLVideoElement>
13
14
  ) {
14
15
  const innerRef = useRef<HTMLVideoElement>(null);
15
16
  const elementId = useMemo(() => id ?? `stream-${getRandomString()}`, [id]);
@@ -56,7 +57,7 @@ const StreamView = forwardRef<HTMLVideoElement, StreamViewProps>(StreamViewBase)
56
57
 
57
58
  StreamView.displayName = 'StreamView';
58
59
 
59
- function LocalStreamViewBase({ muted, mirror, ...props }: StreamViewProps, ref: React.Ref<HTMLVideoElement>) {
60
+ function LocalStreamViewBase({ muted, mirror, ...props }: StreamViewProps, ref: React.ForwardedRef<HTMLVideoElement>) {
60
61
  const isMuted = typeof muted === 'boolean' ? muted : true;
61
62
  const isMirror = typeof mirror === 'boolean' ? mirror : true;
62
63
 
@@ -67,7 +68,7 @@ const LocalStreamView = forwardRef<HTMLVideoElement, StreamViewProps>(LocalStrea
67
68
 
68
69
  LocalStreamView.displayName = 'LocalStreamView';
69
70
 
70
- function RemoteStreamViewBase({ muted, mirror, ...props }: StreamViewProps, ref: React.Ref<HTMLVideoElement>) {
71
+ function RemoteStreamViewBase({ muted, mirror, ...props }: StreamViewProps, ref: React.ForwardedRef<HTMLVideoElement>) {
71
72
  const isMuted = typeof muted === 'boolean' ? muted : false;
72
73
  const isMirror = typeof mirror === 'boolean' ? mirror : false;
73
74
 
@@ -113,7 +114,7 @@ function FullscreenStreamViewBase(
113
114
  pipButtonIconProps,
114
115
  ...props
115
116
  }: FullscreenStreamViewProps,
116
- ref: React.Ref<FullscreenStreamViewRef>
117
+ ref: React.ForwardedRef<FullscreenStreamViewRef>
117
118
  ) {
118
119
  const innerRef = useRef<HTMLDivElement>(null);
119
120
  const [isFullscreen, setIsFullscreen] = useState<boolean>(false);