@umituz/web-design-system 1.1.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/package.json +13 -3
  2. package/src/global.d.ts +20 -0
  3. package/src/infrastructure/error/ErrorDisplay.tsx +6 -12
  4. package/src/infrastructure/error/SuspenseWrapper.tsx +15 -10
  5. package/src/infrastructure/performance/useMemoryOptimization.ts +1 -1
  6. package/src/infrastructure/utils/cn.util.ts +7 -7
  7. package/src/infrastructure/utils/index.ts +1 -1
  8. package/src/presentation/atoms/Input.tsx +1 -1
  9. package/src/presentation/atoms/Slider.tsx +1 -1
  10. package/src/presentation/atoms/Text.tsx +1 -1
  11. package/src/presentation/atoms/Tooltip.tsx +2 -2
  12. package/src/presentation/hooks/index.ts +2 -1
  13. package/src/presentation/hooks/useClickOutside.ts +1 -1
  14. package/src/presentation/molecules/Avatar.tsx +47 -19
  15. package/src/presentation/molecules/CheckboxGroup.tsx +1 -1
  16. package/src/presentation/molecules/FormField.tsx +2 -2
  17. package/src/presentation/molecules/InputGroup.tsx +1 -1
  18. package/src/presentation/molecules/RadioGroup.tsx +1 -1
  19. package/src/presentation/molecules/Select.tsx +152 -33
  20. package/src/presentation/molecules/index.ts +3 -3
  21. package/src/presentation/organisms/Accordion.tsx +53 -111
  22. package/src/presentation/organisms/Alert.tsx +52 -85
  23. package/src/presentation/organisms/Card.tsx +1 -1
  24. package/src/presentation/organisms/Footer.tsx +99 -0
  25. package/src/presentation/organisms/Navbar.tsx +1 -1
  26. package/src/presentation/organisms/Table.tsx +1 -1
  27. package/src/presentation/organisms/index.ts +6 -4
  28. package/src/presentation/templates/Form.tsx +2 -2
  29. package/src/presentation/templates/List.tsx +1 -1
  30. package/src/presentation/templates/PageHeader.tsx +78 -0
  31. package/src/presentation/templates/PageLayout.tsx +38 -0
  32. package/src/presentation/templates/ProjectSkeleton.tsx +35 -0
  33. package/src/presentation/templates/Section.tsx +1 -1
  34. package/src/presentation/templates/index.ts +9 -0
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@umituz/web-design-system",
3
- "version": "1.1.0",
4
- "description": "Web Design System - Atomic Design components (Atoms, Molecules, Organisms) for React applications",
3
+ "version": "1.5.1",
4
+ "private": false,
5
+ "description": "Web Design System - Atomic Design components (Atoms, Molecules, Organisms, Templates) for React applications",
5
6
  "main": "./src/index.ts",
6
7
  "types": "./src/index.ts",
7
8
  "sideEffects": false,
@@ -51,14 +52,23 @@
51
52
  "url": "git+https://github.com/umituz/web-design-system.git"
52
53
  },
53
54
  "peerDependencies": {
55
+ "@radix-ui/react-accordion": ">=1.0.0",
56
+ "@radix-ui/react-select": ">=2.0.0",
57
+ "clsx": ">=2.0.0",
54
58
  "react": ">=18.0.0",
55
- "react-dom": ">=18.0.0"
59
+ "react-dom": ">=18.0.0",
60
+ "tailwind-merge": ">=2.0.0"
56
61
  },
57
62
  "devDependencies": {
63
+ "@radix-ui/react-accordion": "^1.2.12",
64
+ "@radix-ui/react-select": "^2.2.6",
58
65
  "@types/react": "^18.0.0",
59
66
  "@types/react-dom": "^18.0.0",
67
+ "clsx": "^2.1.1",
68
+ "lucide-react": "^0.577.0",
60
69
  "react": "^18.0.0",
61
70
  "react-dom": "^18.0.0",
71
+ "tailwind-merge": "^3.5.0",
62
72
  "typescript": "~5.9.2"
63
73
  },
64
74
  "publishConfig": {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Global Type Declarations
3
+ * @description Global type definitions for the design system
4
+ */
5
+
6
+ declare global {
7
+ interface ImportMetaEnv {
8
+ readonly DEV: boolean;
9
+ readonly MODE: string;
10
+ readonly BASE_URL: string;
11
+ readonly PROD: boolean;
12
+ readonly SSR: boolean;
13
+ }
14
+
15
+ interface ImportMeta {
16
+ readonly env: ImportMetaEnv;
17
+ }
18
+ }
19
+
20
+ export {};
@@ -55,13 +55,9 @@ export const ErrorDisplay: React.FC<ErrorDisplayProps> = ({
55
55
  const errorMessage = error instanceof Error ? error.message : (error || 'Unknown error');
56
56
  const errorStack = error instanceof Error ? error.stack : undefined;
57
57
 
58
- const containerStyle: React.CSSProperties = {
59
- className
60
- };
61
-
62
58
  if (variant === 'minimal') {
63
59
  return (
64
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '14px', color: config.color, ...containerStyle }}>
60
+ <div className={className} style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '14px', color: config.color }}>
65
61
  <span>{config.icon}</span>
66
62
  <span>{errorMessage}</span>
67
63
  </div>
@@ -70,12 +66,11 @@ export const ErrorDisplay: React.FC<ErrorDisplayProps> = ({
70
66
 
71
67
  if (variant === 'inline') {
72
68
  return (
73
- <div style={{
69
+ <div className={className} style={{
74
70
  padding: '12px 16px',
75
71
  background: config.bgColor,
76
72
  border: `1px solid ${config.borderColor}`,
77
- borderRadius: '6px',
78
- ...containerStyle
73
+ borderRadius: '6px'
79
74
  }}>
80
75
  <span style={{ marginRight: '8px' }}>{config.icon}</span>
81
76
  <span style={{ fontWeight: 500 }}>{title}:</span> {errorMessage}
@@ -85,12 +80,11 @@ export const ErrorDisplay: React.FC<ErrorDisplayProps> = ({
85
80
 
86
81
  if (variant === 'card') {
87
82
  return (
88
- <div style={{
83
+ <div className={className} style={{
89
84
  background: 'white',
90
85
  borderRadius: '8px',
91
86
  boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
92
- border: 'none',
93
- ...containerStyle
87
+ border: 'none'
94
88
  }}>
95
89
  <div style={{ padding: '24px' }}>
96
90
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
@@ -209,7 +203,7 @@ export const ErrorDisplay: React.FC<ErrorDisplayProps> = ({
209
203
 
210
204
  // Default variant
211
205
  return (
212
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px', ...containerStyle }}>
206
+ <div className={className} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}>
213
207
  <div style={{
214
208
  width: '100%',
215
209
  maxWidth: '448px',
@@ -8,6 +8,7 @@ export interface SuspenseWrapperProps {
8
8
  loadingText?: string;
9
9
  variant?: 'default' | 'minimal' | 'card';
10
10
  className?: string;
11
+ style?: React.CSSProperties;
11
12
  showErrorBoundary?: boolean;
12
13
  errorBoundaryLevel?: 'page' | 'component' | 'feature';
13
14
  }
@@ -41,6 +42,7 @@ export const SuspenseWrapper: React.FC<SuspenseWrapperProps> = ({
41
42
  loadingText = 'Loading...',
42
43
  variant = 'default',
43
44
  className = '',
45
+ style,
44
46
  showErrorBoundary = true,
45
47
  errorBoundaryLevel = 'component'
46
48
  }) => {
@@ -57,18 +59,21 @@ export const SuspenseWrapper: React.FC<SuspenseWrapperProps> = ({
57
59
  </Suspense>
58
60
  );
59
61
 
60
- if (showErrorBoundary) {
61
- return (
62
- <ErrorBoundary
63
- level={errorBoundaryLevel}
64
- fallback={errorFallback}
65
- >
66
- {suspenseContent}
67
- </ErrorBoundary>
68
- );
62
+ const content = showErrorBoundary ? (
63
+ <ErrorBoundary
64
+ level={errorBoundaryLevel}
65
+ fallback={errorFallback}
66
+ >
67
+ {suspenseContent}
68
+ </ErrorBoundary>
69
+ ) : suspenseContent;
70
+
71
+ // Wrap in div if className or style is provided
72
+ if (className || style) {
73
+ return <div className={className} style={style}>{content}</div>;
69
74
  }
70
75
 
71
- return suspenseContent;
76
+ return content;
72
77
  };
73
78
 
74
79
  // Specialized Suspense wrappers
@@ -249,7 +249,7 @@ export const useMemoryOptimization = (config: MemoryOptimizationConfig = {}) =>
249
249
  };
250
250
 
251
251
  export const useMemoryLeakDetector = (componentName?: string) => {
252
- const mountTime = useRef<number>(() => Date.now());
252
+ const mountTime = useRef<number>(Date.now());
253
253
  const [renderCount, setRenderCount] = useState<number>(0);
254
254
  const [lifespan, setLifespan] = useState<number>(0);
255
255
 
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * cn Utility
3
- * @description Conditional className utility (clsx + tailwind-merge alternative)
3
+ * @description Conditional className utility using clsx + tailwind-merge for proper Tailwind class merging
4
4
  */
5
5
 
6
- export type ClassName = string | undefined | null | false | ClassName[];
6
+ import { clsx, type ClassValue } from 'clsx';
7
+ import { twMerge } from 'tailwind-merge';
7
8
 
8
- export function cn(...classes: ClassName[]): string {
9
- return classes
10
- .flat(Infinity as any)
11
- .filter(Boolean)
12
- .join(' ');
9
+ export type { ClassValue };
10
+
11
+ export function cn(...inputs: ClassValue[]): string {
12
+ return twMerge(clsx(inputs));
13
13
  }
@@ -6,5 +6,5 @@
6
6
 
7
7
  export {
8
8
  cn,
9
- type ClassName,
10
9
  } from './cn.util';
10
+ export type { ClassValue } from './cn.util';
@@ -7,7 +7,7 @@ import { forwardRef, type InputHTMLAttributes } from 'react';
7
7
  import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps, SizeVariant } from '../../domain/types';
9
9
 
10
- export interface InputProps extends InputHTMLAttributes<HTMLInputElement>, BaseProps {
10
+ export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>, BaseProps {
11
11
  error?: boolean;
12
12
  size?: Extract<SizeVariant, 'sm' | 'md' | 'lg'>;
13
13
  }
@@ -7,7 +7,7 @@ import { forwardRef, type InputHTMLAttributes } from 'react';
7
7
  import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps } from '../../domain/types';
9
9
 
10
- export interface SliderProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>, BaseProps {
10
+ export interface SliderProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'value'>, BaseProps {
11
11
  min?: number;
12
12
  max?: number;
13
13
  step?: number;
@@ -7,7 +7,7 @@ import { forwardRef, type HTMLAttributes } from 'react';
7
7
  import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps } from '../../domain/types';
9
9
 
10
- export type TextElement = 'p' | 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
10
+ export type TextElement = 'p' | 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label';
11
11
  export type TextVariant = 'body' | 'heading' | 'label' | 'caption';
12
12
  export type TextSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
13
13
 
@@ -7,7 +7,7 @@ import { forwardRef, type HTMLAttributes, type ReactNode, useState, useRef, useE
7
7
  import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps, ChildrenProps } from '../../domain/types';
9
9
 
10
- export interface TooltipProps extends HTMLAttributes<HTMLDivElement>, BaseProps, ChildrenProps {
10
+ export interface TooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'content'>, BaseProps {
11
11
  content: ReactNode;
12
12
  placement?: 'top' | 'bottom' | 'left' | 'right';
13
13
  delay?: number;
@@ -23,7 +23,7 @@ const placementStyles: Record<'top' | 'bottom' | 'left' | 'right', string> = {
23
23
  export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
24
24
  ({ className, children, content, placement = 'top', delay = 200, ...props }, ref) => {
25
25
  const [isOpen, setIsOpen] = useState(false);
26
- const timeoutRef = useRef<NodeJS.Timeout>();
26
+ const timeoutRef = useRef<number>();
27
27
 
28
28
  const handleMouseEnter = () => {
29
29
  timeoutRef.current = setTimeout(() => {
@@ -15,11 +15,12 @@ export { useLocalStorage } from './useLocalStorage';
15
15
  export { useClickOutside } from './useClickOutside';
16
16
 
17
17
  export { useKeyboard, useEscape } from './useKeyboard';
18
- export type { KeyboardKey, KeyboardModifier, KeyboardOptions, UseClipboardReturn } from './useKeyboard';
18
+ export type { KeyboardKey, KeyboardModifier, KeyboardOptions } from './useKeyboard';
19
19
 
20
20
  export { useDebounce } from './useDebounce';
21
21
 
22
22
  export { useClipboard } from './useClipboard';
23
+ export type { UseClipboardReturn } from './useClipboard';
23
24
 
24
25
  export { useToggle } from './useToggle';
25
26
 
@@ -14,7 +14,7 @@ export function useClickOutside<T extends HTMLElement>(
14
14
  useEffect(() => {
15
15
  if (!enabled) return;
16
16
 
17
- const handleClick = (event: MouseEvent) => {
17
+ const handleClick = (event: Event) => {
18
18
  if (ref.current && !ref.current.contains(event.target as Node)) {
19
19
  callback();
20
20
  }
@@ -1,16 +1,13 @@
1
1
  /**
2
2
  * Avatar Component (Molecule)
3
- * @description User avatar with fallback
3
+ * @description User avatar with image and fallback (shadcn/ui compatible)
4
4
  */
5
5
 
6
- import { forwardRef, type HTMLAttributes } from 'react';
6
+ import { forwardRef, type HTMLAttributes, type ElementType, type ComponentPropsWithoutRef } from 'react';
7
7
  import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps, SizeVariant } from '../../domain/types';
9
9
 
10
10
  export interface AvatarProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
11
- src?: string;
12
- alt?: string;
13
- fallback?: string;
14
11
  size?: Extract<SizeVariant, 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'>;
15
12
  }
16
13
 
@@ -23,30 +20,61 @@ const sizeStyles: Record<'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl', string> = {
23
20
  '2xl': 'h-20 w-20 text-2xl',
24
21
  };
25
22
 
26
- export const Avatar = forwardRef<HTMLDivElement, AvatarProps>(
27
- ({ className, src, alt, fallback, size = 'md', ...props }, ref) => {
28
- const hasError = !src;
29
-
23
+ const Avatar = forwardRef<HTMLDivElement, AvatarProps>(
24
+ ({ className, size = 'md', ...props }, ref) => {
30
25
  return (
31
26
  <div
32
27
  ref={ref}
33
28
  className={cn(
34
- 'relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted',
29
+ 'relative flex shrink-0 overflow-hidden rounded-full',
35
30
  sizeStyles[size],
36
31
  className
37
32
  )}
38
33
  {...props}
39
- >
40
- {hasError ? (
41
- <span className="font-medium text-muted-foreground">
42
- {fallback || '?'}
43
- </span>
44
- ) : (
45
- <img src={src} alt={alt || 'Avatar'} className="h-full w-full object-cover" />
46
- )}
47
- </div>
34
+ />
48
35
  );
49
36
  }
50
37
  );
51
38
 
52
39
  Avatar.displayName = 'Avatar';
40
+
41
+ export interface AvatarImageProps extends ComponentPropsWithoutRef<'img'> {
42
+ asChild?: boolean;
43
+ }
44
+
45
+ const AvatarImage = forwardRef<HTMLImageElement, AvatarImageProps>(
46
+ ({ className, src, alt, ...props }, ref) => {
47
+ return (
48
+ <img
49
+ ref={ref}
50
+ src={src}
51
+ alt={alt}
52
+ className={cn('aspect-square h-full w-full object-cover', className)}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+ );
58
+
59
+ AvatarImage.displayName = 'AvatarImage';
60
+
61
+ export interface AvatarFallbackProps extends HTMLAttributes<HTMLDivElement> {}
62
+
63
+ const AvatarFallback = forwardRef<HTMLDivElement, AvatarFallbackProps>(
64
+ ({ className, ...props }, ref) => {
65
+ return (
66
+ <div
67
+ ref={ref}
68
+ className={cn(
69
+ 'flex h-full w-full items-center justify-center rounded-full bg-muted',
70
+ className
71
+ )}
72
+ {...props}
73
+ />
74
+ );
75
+ }
76
+ );
77
+
78
+ AvatarFallback.displayName = 'AvatarFallback';
79
+
80
+ export { Avatar, AvatarImage, AvatarFallback };
@@ -15,7 +15,7 @@ export interface CheckboxOption {
15
15
  disabled?: boolean;
16
16
  }
17
17
 
18
- export interface CheckboxGroupProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
18
+ export interface CheckboxGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>, BaseProps {
19
19
  options: CheckboxOption[];
20
20
  value?: string[];
21
21
  onChange?: (values: string[]) => void;
@@ -27,10 +27,10 @@ export const FormField = forwardRef<HTMLInputElement, FormFieldProps>(
27
27
  return (
28
28
  <div className={cn('space-y-1.5', className)} {...props}>
29
29
  {label && (
30
- <Text as="label" variant="label" size="sm" htmlFor={fieldId}>
30
+ <label htmlFor={fieldId} className="text-sm font-medium">
31
31
  {label}
32
32
  {required && <span className="text-destructive ml-1">*</span>}
33
- </Text>
33
+ </label>
34
34
  )}
35
35
 
36
36
  <Input
@@ -8,7 +8,7 @@ import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps, ChildrenProps } from '../../domain/types';
9
9
  import { Input } from '../atoms/Input';
10
10
 
11
- export interface InputGroupProps extends HTMLAttributes<HTMLDivElement>, BaseProps, ChildrenProps {
11
+ export interface InputGroupProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
12
12
  leftElement?: ReactNode;
13
13
  rightElement?: ReactNode;
14
14
  }
@@ -15,7 +15,7 @@ export interface RadioOption {
15
15
  disabled?: boolean;
16
16
  }
17
17
 
18
- export interface RadioGroupProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
18
+ export interface RadioGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>, BaseProps {
19
19
  name: string;
20
20
  options: RadioOption[];
21
21
  value?: string;
@@ -1,41 +1,160 @@
1
1
  /**
2
2
  * Select Component (Molecule)
3
- * @description Dropdown select input
3
+ * @description Dropdown select (Shadcn/Radix UI compatible)
4
4
  */
5
5
 
6
- import { forwardRef, type SelectHTMLAttributes } from 'react';
6
+ import * as React from 'react';
7
+ import * as SelectPrimitive from '@radix-ui/react-select';
8
+ import { Check, ChevronDown, ChevronUp } from 'lucide-react';
7
9
  import { cn } from '../../infrastructure/utils';
8
- import type { BaseProps } from '../../domain/types';
9
-
10
- export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement>, BaseProps {
11
- error?: boolean;
12
- options: Array<{ value: string; label: string; disabled?: boolean }>;
13
- }
14
-
15
- export const Select = forwardRef<HTMLSelectElement, SelectProps>(
16
- ({ className, error, options, disabled, ...props }, ref) => {
17
- return (
18
- <select
19
- ref={ref}
20
- disabled={disabled}
10
+
11
+ const Select = SelectPrimitive.Root;
12
+
13
+ const SelectGroup = SelectPrimitive.Group;
14
+
15
+ const SelectValue = SelectPrimitive.Value;
16
+
17
+ const SelectTrigger = React.forwardRef<
18
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
19
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
20
+ >(({ className, children, ...props }, ref) => (
21
+ <SelectPrimitive.Trigger
22
+ ref={ref}
23
+ className={cn(
24
+ 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
25
+ className
26
+ )}
27
+ {...props}
28
+ >
29
+ {children}
30
+ <SelectPrimitive.Icon asChild>
31
+ <ChevronDown className="h-4 w-4 opacity-50" />
32
+ </SelectPrimitive.Icon>
33
+ </SelectPrimitive.Trigger>
34
+ ));
35
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
36
+
37
+ const SelectScrollUpButton = React.forwardRef<
38
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
39
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
40
+ >(({ className, ...props }, ref) => (
41
+ <SelectPrimitive.ScrollUpButton
42
+ ref={ref}
43
+ className={cn(
44
+ 'flex cursor-default items-center justify-center py-1',
45
+ className
46
+ )}
47
+ {...props}
48
+ >
49
+ <ChevronUp className="h-4 w-4" />
50
+ </SelectPrimitive.ScrollUpButton>
51
+ ));
52
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
53
+
54
+ const SelectScrollDownButton = React.forwardRef<
55
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
56
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
57
+ >(({ className, ...props }, ref) => (
58
+ <SelectPrimitive.ScrollDownButton
59
+ ref={ref}
60
+ className={cn(
61
+ 'flex cursor-default items-center justify-center py-1',
62
+ className
63
+ )}
64
+ {...props}
65
+ >
66
+ <ChevronDown className="h-4 w-4" />
67
+ </SelectPrimitive.ScrollDownButton>
68
+ ));
69
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
70
+
71
+ const SelectContent = React.forwardRef<
72
+ React.ElementRef<typeof SelectPrimitive.Content>,
73
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
74
+ >(({ className, children, position = 'popper', ...props }, ref) => (
75
+ <SelectPrimitive.Portal>
76
+ <SelectPrimitive.Content
77
+ ref={ref}
78
+ className={cn(
79
+ 'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
80
+ position === 'popper' &&
81
+ 'data-[side=bottom]:translate-y-1 data-[side=left]:translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:translate-y-1',
82
+ className
83
+ )}
84
+ position={position}
85
+ {...props}
86
+ >
87
+ <SelectScrollUpButton />
88
+ <SelectPrimitive.Viewport
21
89
  className={cn(
22
- 'flex h-9 w-full rounded-md border border-input bg-background px-3 py-2',
23
- 'text-sm ring-offset-background',
24
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
25
- 'disabled:cursor-not-allowed disabled:opacity-50',
26
- error && 'border-destructive',
27
- className
90
+ 'p-1',
91
+ position === 'popper' &&
92
+ 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
28
93
  )}
29
- {...props}
30
94
  >
31
- {options.map((option) => (
32
- <option key={option.value} value={option.value} disabled={option.disabled}>
33
- {option.label}
34
- </option>
35
- ))}
36
- </select>
37
- );
38
- }
39
- );
40
-
41
- Select.displayName = 'Select';
95
+ {children}
96
+ </SelectPrimitive.Viewport>
97
+ <SelectScrollDownButton />
98
+ </SelectPrimitive.Content>
99
+ </SelectPrimitive.Portal>
100
+ ));
101
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
102
+
103
+ const SelectLabel = React.forwardRef<
104
+ React.ElementRef<typeof SelectPrimitive.Label>,
105
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
106
+ >(({ className, ...props }, ref) => (
107
+ <SelectPrimitive.Label
108
+ ref={ref}
109
+ className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
110
+ {...props}
111
+ />
112
+ ));
113
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
114
+
115
+ const SelectItem = React.forwardRef<
116
+ React.ElementRef<typeof SelectPrimitive.Item>,
117
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
118
+ >(({ className, children, ...props }, ref) => (
119
+ <SelectPrimitive.Item
120
+ ref={ref}
121
+ className={cn(
122
+ 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
123
+ className
124
+ )}
125
+ {...props}
126
+ >
127
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
128
+ <SelectPrimitive.ItemIndicator>
129
+ <Check className="h-4 w-4" />
130
+ </SelectPrimitive.ItemIndicator>
131
+ </span>
132
+ {children}
133
+ </SelectPrimitive.Item>
134
+ ));
135
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
136
+
137
+ const SelectSeparator = React.forwardRef<
138
+ React.ElementRef<typeof SelectPrimitive.Separator>,
139
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
140
+ >(({ className, ...props }, ref) => (
141
+ <SelectPrimitive.Separator
142
+ ref={ref}
143
+ className={cn('-mx-1 my-1 h-px bg-muted', className)}
144
+ {...props}
145
+ />
146
+ ));
147
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
148
+
149
+ export {
150
+ Select,
151
+ SelectGroup,
152
+ SelectValue,
153
+ SelectTrigger,
154
+ SelectContent,
155
+ SelectLabel,
156
+ SelectItem,
157
+ SelectSeparator,
158
+ SelectScrollUpButton,
159
+ SelectScrollDownButton,
160
+ };
@@ -10,8 +10,8 @@ export type { FormFieldProps } from './FormField';
10
10
  export { SearchBox } from './SearchBox';
11
11
  export type { SearchBoxProps } from './SearchBox';
12
12
 
13
- export { Avatar } from './Avatar';
14
- export type { AvatarProps } from './Avatar';
13
+ export { Avatar, AvatarImage, AvatarFallback } from './Avatar';
14
+ export type { AvatarProps, AvatarImageProps, AvatarFallbackProps } from './Avatar';
15
15
 
16
16
  export { Chip } from './Chip';
17
17
  export type { ChipProps } from './Chip';
@@ -19,7 +19,7 @@ export type { ChipProps } from './Chip';
19
19
  export { Toggle } from './Toggle';
20
20
  export type { ToggleProps } from './Toggle';
21
21
 
22
- export { Select } from './Select';
22
+ export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton } from './Select';
23
23
  export type { SelectProps } from './Select';
24
24
 
25
25
  export { Textarea } from './Textarea';