@umituz/web-design-system 3.1.7 → 3.1.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-design-system",
3
- "version": "3.1.7",
3
+ "version": "3.1.9",
4
4
  "private": false,
5
5
  "description": "Web Design System - Atomic Design components (Atoms, Molecules, Organisms, Templates) for React applications",
6
6
  "main": "./src/index.ts",
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * cn Utility
3
- * @description Conditional className utility using clsx + tailwind-merge for proper Tailwind class merging
3
+ * @description Conditional className utility using clsx + tailwind-merge for proper Tailwind class merging with LRU cache for performance
4
4
  */
5
5
 
6
6
  import { clsx, type ClassValue } from 'clsx';
@@ -8,6 +8,65 @@ import { twMerge } from 'tailwind-merge';
8
8
 
9
9
  export type { ClassValue };
10
10
 
11
+ // Simple LRU Cache implementation for className caching
12
+ class LRUCache<K, V> {
13
+ private cache: Map<K, V>;
14
+ private maxSize: number;
15
+
16
+ constructor(maxSize: number = 128) {
17
+ this.cache = new Map();
18
+ this.maxSize = maxSize;
19
+ }
20
+
21
+ get(key: K): V | undefined {
22
+ const value = this.cache.get(key);
23
+ if (value !== undefined) {
24
+ // Move to end (most recently used)
25
+ this.cache.delete(key);
26
+ this.cache.set(key, value);
27
+ }
28
+ return value;
29
+ }
30
+
31
+ set(key: K, value: V): void {
32
+ // Remove existing entry to update position
33
+ if (this.cache.has(key)) {
34
+ this.cache.delete(key);
35
+ }
36
+ // Remove oldest entry if at capacity
37
+ else if (this.cache.size >= this.maxSize) {
38
+ const firstKey = this.cache.keys().next().value;
39
+ this.cache.delete(firstKey);
40
+ }
41
+ this.cache.set(key, value);
42
+ }
43
+
44
+ clear(): void {
45
+ this.cache.clear();
46
+ }
47
+ }
48
+
49
+ // Create cache instance
50
+ const classNameCache = new LRUCache<string, string>(256);
51
+
52
+ // Cache key generator
53
+ function generateCacheKey(inputs: ClassValue[]): string {
54
+ return JSON.stringify(inputs);
55
+ }
56
+
11
57
  export function cn(...inputs: ClassValue[]): string {
12
- return twMerge(clsx(inputs));
58
+ const cacheKey = generateCacheKey(inputs);
59
+ const cached = classNameCache.get(cacheKey);
60
+
61
+ if (cached) {
62
+ return cached;
63
+ }
64
+
65
+ const result = twMerge(clsx(inputs));
66
+ classNameCache.set(cacheKey, result);
67
+
68
+ return result;
13
69
  }
70
+
71
+ // Export cache for manual clearing if needed
72
+ export { classNameCache };
@@ -41,12 +41,12 @@ export interface ButtonProps
41
41
  asChild?: boolean;
42
42
  }
43
43
 
44
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
+ const Button = React.memo(React.forwardRef<HTMLButtonElement, ButtonProps>(
45
45
  ({ className, variant, size, asChild = false, ...props }, ref) => {
46
46
  const Comp = asChild ? Slot : 'button';
47
47
  return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
48
48
  }
49
- );
49
+ ));
50
50
  Button.displayName = 'Button';
51
51
 
52
52
  export { Button, buttonVariants };
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { forwardRef, type HTMLAttributes } from 'react';
7
+ import React from 'react';
7
8
  import { cn } from '../../infrastructure/utils';
8
9
  import type { BaseProps } from '../../domain/types';
9
10
 
@@ -45,7 +46,7 @@ const weightStyles: Record<'normal' | 'medium' | 'semibold' | 'bold', string> =
45
46
 
46
47
  // NOTE: "as any" is used here for the polymorphic ref, which is a necessary workaround
47
48
  // for TypeScript's limitations in typing polymorphic components properly.
48
- export const Text = forwardRef<HTMLElement, TextProps>(
49
+ export const Text = React.memo(forwardRef<HTMLElement, TextProps>(
49
50
  ({ className, as = 'p', variant = 'body', size = 'md', weight = 'normal', ...props }, ref) => {
50
51
  const Tag = as as any;
51
52
  return (
@@ -61,6 +62,6 @@ export const Text = forwardRef<HTMLElement, TextProps>(
61
62
  />
62
63
  );
63
64
  }
64
- );
65
+ ));
65
66
 
66
67
  Text.displayName = 'Text';
@@ -1,22 +1,65 @@
1
1
  /**
2
2
  * useDebounce Hook
3
- * @description Debounce a value
3
+ * @description Debounce a value with optimized performance
4
4
  */
5
5
 
6
- import { useState, useEffect } from 'react';
6
+ import { useState, useEffect, useRef } from 'react';
7
7
 
8
8
  export function useDebounce<T>(value: T, delay: number = 500): T {
9
9
  const [debouncedValue, setDebouncedValue] = useState<T>(value);
10
+ const timeoutRef = useRef<number>();
10
11
 
11
12
  useEffect(() => {
12
- const handler = setTimeout(() => {
13
+ // Clear previous timeout
14
+ if (timeoutRef.current) {
15
+ clearTimeout(timeoutRef.current);
16
+ }
17
+
18
+ // Set new timeout
19
+ timeoutRef.current = window.setTimeout(() => {
13
20
  setDebouncedValue(value);
14
21
  }, delay);
15
22
 
23
+ // Cleanup
16
24
  return () => {
17
- clearTimeout(handler);
25
+ if (timeoutRef.current) {
26
+ clearTimeout(timeoutRef.current);
27
+ }
18
28
  };
19
29
  }, [value, delay]);
20
30
 
21
31
  return debouncedValue;
22
32
  }
33
+
34
+ /**
35
+ * useThrottle Hook
36
+ * @description Throttle a function to limit execution rate
37
+ */
38
+ export function useThrottle<T extends (...args: any[]) => any>(
39
+ func: T,
40
+ delay: number = 300
41
+ ): T {
42
+ const lastRun = useRef<Date>(new Date());
43
+ const timeoutRef = useRef<number>();
44
+
45
+ return useCallback((...args: Parameters<T>) => {
46
+ const now = new Date();
47
+ const timeSinceLastRun = now.getTime() - lastRun.current.getTime();
48
+
49
+ if (timeSinceLastRun >= delay) {
50
+ lastRun.current = now;
51
+ return func(...args);
52
+ }
53
+
54
+ if (timeoutRef.current) {
55
+ clearTimeout(timeoutRef.current);
56
+ }
57
+
58
+ timeoutRef.current = window.setTimeout(() => {
59
+ lastRun.current = new Date();
60
+ func(...args);
61
+ }, delay - timeSinceLastRun);
62
+ }, [func, delay]) as T;
63
+ }
64
+
65
+ import { useCallback } from 'react';
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * useLocalStorage Hook
3
- * @description LocalStorage state management
3
+ * @description LocalStorage state management with optimized re-renders
4
4
  */
5
5
 
6
- import { useCallback, useState } from 'react';
6
+ import { useCallback, useState, useRef } from 'react';
7
7
 
8
8
  export function useLocalStorage<T>(
9
9
  key: string,
@@ -18,17 +18,21 @@ export function useLocalStorage<T>(
18
18
  }
19
19
  });
20
20
 
21
+ // Use ref to track latest value without causing re-renders
22
+ const valueRef = useRef(storedValue);
23
+ valueRef.current = storedValue;
24
+
21
25
  const setValue = useCallback(
22
26
  (value: T | ((prev: T) => T)) => {
23
27
  try {
24
- const valueToStore = value instanceof Function ? value(storedValue) : value;
28
+ const valueToStore = value instanceof Function ? value(valueRef.current) : value;
25
29
  setStoredValue(valueToStore);
26
30
  window.localStorage.setItem(key, JSON.stringify(valueToStore));
27
31
  } catch (error) {
28
32
  console.error(`Error setting localStorage key "${key}":`, error);
29
33
  }
30
34
  },
31
- [key, storedValue]
35
+ [key] // Remove storedValue dependency
32
36
  );
33
37
 
34
38
  const removeValue = useCallback(() => {
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { forwardRef, type HTMLAttributes } from 'react';
7
+ import React from 'react';
7
8
  import { cn } from '../../infrastructure/utils';
8
9
  import type { BaseProps } from '../../domain/types';
9
10
 
@@ -33,7 +34,7 @@ export const Card = forwardRef<HTMLDivElement, CardProps>(
33
34
 
34
35
  Card.displayName = 'Card';
35
36
 
36
- export const CardHeader = forwardRef<HTMLDivElement, CardProps>(
37
+ export const CardHeader = React.memo(forwardRef<HTMLDivElement, CardProps>(
37
38
  ({ className, ...props }, ref) => (
38
39
  <div
39
40
  ref={ref}
@@ -41,11 +42,11 @@ export const CardHeader = forwardRef<HTMLDivElement, CardProps>(
41
42
  {...props}
42
43
  />
43
44
  )
44
- );
45
+ ));
45
46
 
46
47
  CardHeader.displayName = 'CardHeader';
47
48
 
48
- export const CardTitle = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLHeadingElement>>(
49
+ export const CardTitle = React.memo(forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLHeadingElement>>(
49
50
  ({ className, ...props }, ref) => (
50
51
  <h3
51
52
  ref={ref}
@@ -53,11 +54,11 @@ export const CardTitle = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLHea
53
54
  {...props}
54
55
  />
55
56
  )
56
- );
57
+ ));
57
58
 
58
59
  CardTitle.displayName = 'CardTitle';
59
60
 
60
- export const CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
61
+ export const CardDescription = React.memo(forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
61
62
  ({ className, ...props }, ref) => (
62
63
  <p
63
64
  ref={ref}
@@ -65,19 +66,19 @@ export const CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<H
65
66
  {...props}
66
67
  />
67
68
  )
68
- );
69
+ ));
69
70
 
70
71
  CardDescription.displayName = 'CardDescription';
71
72
 
72
- export const CardContent = forwardRef<HTMLDivElement, CardProps>(
73
+ export const CardContent = React.memo(forwardRef<HTMLDivElement, CardProps>(
73
74
  ({ className, ...props }, ref) => (
74
75
  <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
75
76
  )
76
- );
77
+ ));
77
78
 
78
79
  CardContent.displayName = 'CardContent';
79
80
 
80
- export const CardFooter = forwardRef<HTMLDivElement, CardProps>(
81
+ export const CardFooter = React.memo(forwardRef<HTMLDivElement, CardProps>(
81
82
  ({ className, ...props }, ref) => (
82
83
  <div
83
84
  ref={ref}
@@ -85,6 +86,6 @@ export const CardFooter = forwardRef<HTMLDivElement, CardProps>(
85
86
  {...props}
86
87
  />
87
88
  )
88
- );
89
+ ));
89
90
 
90
91
  CardFooter.displayName = 'CardFooter';
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * FilterBar Component (Organism)
3
- * @description Mobile filter bar with search, categories, tags, and sort
3
+ * @description Mobile filter bar with search, categories, tags, and sort with optimized performance
4
4
  */
5
5
 
6
- import { useState } from 'react';
6
+ import { useState, useCallback, memo } from 'react';
7
7
  import React from 'react';
8
8
  import type { BaseProps } from '../../domain/types';
9
9
 
@@ -35,7 +35,67 @@ export interface FilterBarProps extends BaseProps {
35
35
  onClearFilters: () => void;
36
36
  }
37
37
 
38
- export const FilterBar = ({
38
+ // Memoize category button component
39
+ const CategoryButton = memo<{
40
+ category: Category;
41
+ selectedCategory: string | null;
42
+ onCategoryChange: (category: string) => void;
43
+ }>(({ category, selectedCategory, onCategoryChange }) => {
44
+ const Icon = category.icon;
45
+ const isActive = selectedCategory === category.id;
46
+
47
+ const handleClick = useCallback(() => {
48
+ onCategoryChange(category.id);
49
+ }, [category.id, onCategoryChange]);
50
+
51
+ return (
52
+ <button
53
+ onClick={handleClick}
54
+ className={`flex items-center gap-2 px-3 py-2 rounded-lg text-xs transition-all transition-theme ${
55
+ isActive
56
+ ? 'bg-primary-light text-text-primary font-medium'
57
+ : 'bg-bg-secondary text-text-secondary hover:bg-bg-tertiary hover:text-text-primary'
58
+ }`}
59
+ type="button"
60
+ aria-pressed={isActive}
61
+ >
62
+ <Icon size={14} />
63
+ <span>{category.name}</span>
64
+ </button>
65
+ );
66
+ });
67
+
68
+ CategoryButton.displayName = 'CategoryButton';
69
+
70
+ // Memoize tag button component
71
+ const TagButton = memo<{
72
+ tag: string;
73
+ isSelected: boolean;
74
+ onTagToggle: (tag: string) => void;
75
+ }>(({ tag, isSelected, onTagToggle }) => {
76
+ const handleClick = useCallback(() => {
77
+ onTagToggle(tag);
78
+ }, [tag, onTagToggle]);
79
+
80
+ return (
81
+ <button
82
+ onClick={handleClick}
83
+ className={`px-3 py-1.5 rounded-full text-xs font-medium transition-all transition-theme ${
84
+ isSelected
85
+ ? 'bg-primary-light text-text-primary'
86
+ : 'bg-bg-secondary text-text-secondary hover:bg-bg-tertiary hover:text-text-primary'
87
+ }`}
88
+ type="button"
89
+ aria-pressed={isSelected}
90
+ >
91
+ {tag}
92
+ </button>
93
+ );
94
+ });
95
+
96
+ TagButton.displayName = 'TagButton';
97
+
98
+ export const FilterBar = memo<FilterBarProps>(({
39
99
  searchQuery,
40
100
  setSearchQuery,
41
101
  selectedCategory,
@@ -50,9 +110,25 @@ export const FilterBar = ({
50
110
  hasActiveFilters,
51
111
  onClearFilters,
52
112
  className,
53
- }: FilterBarProps) => {
113
+ }) => {
54
114
  const [isFilterOpen, setIsFilterOpen] = useState(false);
55
115
 
116
+ const handleSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
117
+ setSearchQuery(e.target.value);
118
+ }, [setSearchQuery]);
119
+
120
+ const handleToggleFilters = useCallback(() => {
121
+ setIsFilterOpen(prev => !prev);
122
+ }, []);
123
+
124
+ const handleSortChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
125
+ onSortChange(e.target.value);
126
+ }, [onSortChange]);
127
+
128
+ const handleClearFilters = useCallback(() => {
129
+ onClearFilters();
130
+ }, [onClearFilters]);
131
+
56
132
  return (
57
133
  <div className={`lg:hidden mb-4 space-y-3 ${className || ''}`}>
58
134
  {/* Search */}
@@ -75,7 +151,7 @@ export const FilterBar = ({
75
151
  type="text"
76
152
  placeholder="Search..."
77
153
  value={searchQuery}
78
- onChange={(e) => setSearchQuery(e.target.value)}
154
+ onChange={handleSearchChange}
79
155
  className="w-full pl-10 pr-4 py-2.5 bg-bg-secondary text-text-primary rounded-lg border border-border focus:border-primary-light focus:outline-none placeholder-text-secondary/50 transition-theme text-sm"
80
156
  aria-label="Search"
81
157
  />
@@ -84,7 +160,7 @@ export const FilterBar = ({
84
160
  {/* Filter Toggle + Sort + Clear Filters */}
85
161
  <div className="flex items-center gap-2 flex-wrap">
86
162
  <button
87
- onClick={() => setIsFilterOpen(!isFilterOpen)}
163
+ onClick={handleToggleFilters}
88
164
  className="flex items-center gap-2 px-4 py-2.5 bg-bg-secondary text-text-primary rounded-lg border border-border hover:border-primary-light transition-all text-sm font-medium transition-theme"
89
165
  type="button"
90
166
  aria-expanded={isFilterOpen}
@@ -104,7 +180,7 @@ export const FilterBar = ({
104
180
  {/* Sort Dropdown */}
105
181
  <select
106
182
  value={sortBy}
107
- onChange={(e) => onSortChange(e.target.value)}
183
+ onChange={handleSortChange}
108
184
  className="px-3 py-2.5 bg-bg-secondary text-text-primary rounded-lg border border-border focus:border-primary-light focus:outline-none transition-theme text-sm"
109
185
  aria-label="Sort by"
110
186
  >
@@ -118,7 +194,7 @@ export const FilterBar = ({
118
194
  {/* Clear Filters */}
119
195
  {hasActiveFilters && (
120
196
  <button
121
- onClick={onClearFilters}
197
+ onClick={handleClearFilters}
122
198
  className="px-3 py-2.5 text-text-secondary hover:text-text-primary text-sm transition-colors"
123
199
  type="button"
124
200
  >
@@ -134,28 +210,14 @@ export const FilterBar = ({
134
210
  <div>
135
211
  <h4 className="text-sm font-semibold text-text-primary mb-3">Categories</h4>
136
212
  <div className="grid grid-cols-2 gap-2">
137
- {categories.map((category) => {
138
- const Icon = category.icon;
139
- const isActive = selectedCategory === category.id;
140
- return (
141
- <button
142
- key={category.id}
143
- onClick={() => {
144
- onCategoryChange(category.id);
145
- }}
146
- className={`flex items-center gap-2 px-3 py-2 rounded-lg text-xs transition-all transition-theme ${
147
- isActive
148
- ? 'bg-primary-light text-text-primary font-medium'
149
- : 'bg-bg-secondary text-text-secondary hover:bg-bg-tertiary hover:text-text-primary'
150
- }`}
151
- type="button"
152
- aria-pressed={isActive}
153
- >
154
- <Icon size={14} />
155
- <span>{category.name}</span>
156
- </button>
157
- );
158
- })}
213
+ {categories.map((category) => (
214
+ <CategoryButton
215
+ key={category.id}
216
+ category={category}
217
+ selectedCategory={selectedCategory}
218
+ onCategoryChange={onCategoryChange}
219
+ />
220
+ ))}
159
221
  </div>
160
222
  </div>
161
223
 
@@ -164,19 +226,12 @@ export const FilterBar = ({
164
226
  <h4 className="text-sm font-semibold text-text-primary mb-3">Popular Tags</h4>
165
227
  <div className="flex flex-wrap gap-2">
166
228
  {popularTags.map((tag) => (
167
- <button
229
+ <TagButton
168
230
  key={tag}
169
- onClick={() => onTagToggle(tag)}
170
- className={`px-3 py-1.5 rounded-full text-xs font-medium transition-all transition-theme ${
171
- selectedTags.includes(tag)
172
- ? 'bg-primary-light text-text-primary'
173
- : 'bg-bg-secondary text-text-secondary hover:bg-bg-tertiary hover:text-text-primary'
174
- }`}
175
- type="button"
176
- aria-pressed={selectedTags.includes(tag)}
177
- >
178
- {tag}
179
- </button>
231
+ tag={tag}
232
+ isSelected={selectedTags.includes(tag)}
233
+ onTagToggle={onTagToggle}
234
+ />
180
235
  ))}
181
236
  </div>
182
237
  </div>
@@ -184,4 +239,6 @@ export const FilterBar = ({
184
239
  )}
185
240
  </div>
186
241
  );
187
- };
242
+ });
243
+
244
+ FilterBar.displayName = 'FilterBar';
@@ -1,21 +1,17 @@
1
1
  /**
2
2
  * Footer Organism Component
3
- * @description Footer with brand info, links, and social icons
3
+ * @description Minimal footer with brand and social icons - optimized for performance
4
4
  */
5
5
 
6
- import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';
6
+ import { forwardRef, type HTMLAttributes, type ReactNode, memo } from 'react';
7
+ import React from 'react';
7
8
  import { cn } from '../../infrastructure/utils';
8
9
  import type { BaseProps } from '../../domain/types';
9
10
 
10
11
  export interface FooterProps extends HTMLAttributes<HTMLElement>, BaseProps {
11
12
  brand?: {
12
13
  name: string;
13
- description?: string;
14
14
  };
15
- sections?: Array<{
16
- title: string;
17
- links: Array<{ label: string; href: string }>;
18
- }>;
19
15
  social?: Array<{
20
16
  name: string;
21
17
  href: string;
@@ -24,106 +20,61 @@ export interface FooterProps extends HTMLAttributes<HTMLElement>, BaseProps {
24
20
  copyright?: string;
25
21
  }
26
22
 
27
- export const Footer = forwardRef<HTMLElement, FooterProps>(
28
- ({ className, brand, sections, social, copyright = '© 2026 UmitUZ. All rights reserved.', ...props }, ref) => {
23
+ // Memoize social icon component to prevent unnecessary re-renders
24
+ const SocialIcon = memo<{
25
+ item: FooterProps['social'][number];
26
+ }>(({ item }) => (
27
+ <a
28
+ href={item.href}
29
+ target="_blank"
30
+ rel="noopener noreferrer"
31
+ className="flex items-center justify-center w-10 h-10 rounded-lg bg-bg-secondary/50 text-text-secondary hover:bg-primary-gradient hover:text-white transition-all duration-200"
32
+ aria-label={item.name}
33
+ title={item.name}
34
+ >
35
+ <span className="relative">{item.icon}</span>
36
+ </a>
37
+ ));
38
+
39
+ SocialIcon.displayName = 'SocialIcon';
40
+
41
+ export const Footer = memo(forwardRef<HTMLElement, FooterProps>(
42
+ ({ className, brand, social, copyright = '© 2026 UmitUZ. All rights reserved.', ...props }, ref) => {
29
43
  return (
30
44
  <footer
31
45
  ref={ref}
32
- className={cn('relative mt-12 transition-theme overflow-hidden', className)}
46
+ className={cn('relative mt-8 transition-theme', className)}
33
47
  {...props}
34
48
  >
35
- <style>{`
36
- @keyframes gradient-shift {
37
- 0%, 100% { opacity: 0.3; transform: scale(1) translate(0, 0); }
38
- 50% { opacity: 0.5; transform: scale(1.1) translate(-10px, -10px); }
39
- }
40
- .animate-gradient-shift {
41
- animation: gradient-shift 8s ease-in-out infinite;
42
- }
43
- `}</style>
44
-
45
- {/* Animated Gradient Background */}
46
- <div className="absolute inset-0 bg-gradient-to-br from-bg-card via-bg-secondary/20 to-bg-card"></div>
47
- <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-primary/8 via-transparent to-transparent animate-gradient-shift"></div>
48
-
49
- {/* Gradient Top Border */}
50
- <div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-primary/50 to-transparent"></div>
51
-
52
- <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 sm:py-16">
53
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10 lg:gap-12">
54
- {/* Brand Section - Prominent */}
49
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
50
+ <div className="flex flex-col md:flex-row items-center justify-between gap-6">
51
+ {/* Brand */}
55
52
  {brand && (
56
- <div className="lg:col-span-1 space-y-6">
57
- <div className="space-y-2">
58
- <h3 className="text-2xl font-bold text-text-primary tracking-tight">
59
- {brand.name}
60
- </h3>
61
- {brand.description && (
62
- <p className="text-text-secondary text-sm leading-relaxed">{brand.description}</p>
63
- )}
64
- </div>
65
-
66
- {/* Social Icons */}
67
- {social && social.length > 0 && (
68
- <div className="flex items-center gap-3">
69
- {social.map((item, index) => (
70
- <a
71
- key={index}
72
- href={item.href}
73
- target="_blank"
74
- rel="noopener noreferrer"
75
- className="group relative flex items-center justify-center w-12 h-12 rounded-xl bg-bg-secondary/50 text-text-secondary hover:bg-primary-gradient hover:text-white border border-border/50 hover:border-transparent transition-all duration-300 hover:scale-110 hover:shadow-lg hover:shadow-primary/20"
76
- aria-label={item.name}
77
- title={item.name}
78
- >
79
- {/* Glow effect */}
80
- <span className="absolute inset-0 rounded-xl bg-primary/15 blur-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"></span>
81
- {/* Icon */}
82
- <span className="relative">{item.icon}</span>
83
- </a>
84
- ))}
85
- </div>
86
- )}
53
+ <div className="text-xl font-bold text-text-primary">
54
+ {brand.name}
87
55
  </div>
88
56
  )}
89
57
 
90
- {/* Links Sections */}
91
- <div className="md:col-span-1 lg:col-span-2">
92
- <div className="grid grid-cols-2 sm:grid-cols-3 gap-10">
93
- {sections?.map((section, index) => (
94
- <div key={index} className="space-y-3">
95
- <h4 className="font-semibold text-text-primary text-sm uppercase tracking-wider">{section.title}</h4>
96
- <ul className="space-y-2">
97
- {section.links.map((link, linkIndex) => (
98
- <li key={linkIndex}>
99
- <a
100
- href={link.href}
101
- className="text-text-secondary text-sm hover:text-primary-light transition-colors duration-200 inline-flex items-center gap-2 group"
102
- >
103
- <span className="opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-primary-light text-xs">→</span>
104
- <span>{link.label}</span>
105
- </a>
106
- </li>
107
- ))}
108
- </ul>
109
- </div>
58
+ {/* Social Icons */}
59
+ {social && social.length > 0 && (
60
+ <div className="flex items-center gap-3">
61
+ {social.map((item, index) => (
62
+ <SocialIcon key={`${item.name}-${index}`} item={item} />
110
63
  ))}
111
64
  </div>
112
- </div>
65
+ )}
113
66
  </div>
114
67
 
115
- {/* Copyright Bar */}
68
+ {/* Copyright */}
116
69
  {copyright && (
117
- <div className="border-t border-border/30 mt-12 pt-6">
118
- <div className="flex flex-col sm:flex-row items-center justify-center gap-4 text-center">
119
- <p className="text-sm text-text-secondary/80">{copyright}</p>
120
- </div>
70
+ <div className="text-center mt-6 pt-6 border-t border-border/20">
71
+ <p className="text-sm text-text-secondary/60">{copyright}</p>
121
72
  </div>
122
73
  )}
123
74
  </div>
124
75
  </footer>
125
76
  );
126
77
  }
127
- );
78
+ ));
128
79
 
129
80
  Footer.displayName = 'Footer';
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Modal Component (Organism)
3
- * @description Dialog/overlay container
3
+ * @description Dialog/overlay container with optimized transitions
4
4
  */
5
5
 
6
- import { forwardRef, type HTMLAttributes } from 'react';
6
+ import { forwardRef, type HTMLAttributes, useEffect, useState } from 'react';
7
+ import React from 'react';
7
8
  import { cn } from '../../infrastructure/utils';
8
9
  import type { BaseProps } from '../../domain/types';
9
10
 
@@ -22,9 +23,35 @@ const sizeStyles: Record<'sm' | 'md' | 'lg' | 'xl' | 'full', string> = {
22
23
  full: 'max-w-full mx-4',
23
24
  };
24
25
 
25
- export const Modal = forwardRef<HTMLDivElement, ModalProps>(
26
+ export const Modal = React.memo(forwardRef<HTMLDivElement, ModalProps>(
26
27
  ({ open = false, onClose, showCloseButton = true, size = 'md', className, children, ...props }, ref) => {
27
- if (!open) return null;
28
+ const [shouldRender, setShouldRender] = useState(open);
29
+ const [isAnimating, setIsAnimating] = useState(false);
30
+
31
+ useEffect(() => {
32
+ if (open) {
33
+ setShouldRender(true);
34
+ // Small delay to trigger animation
35
+ requestAnimationFrame(() => {
36
+ setIsAnimating(true);
37
+ });
38
+ } else {
39
+ setIsAnimating(false);
40
+ // Wait for animation to complete before unmounting
41
+ const timer = setTimeout(() => {
42
+ setShouldRender(false);
43
+ }, 200); // Match animation duration
44
+ return () => clearTimeout(timer);
45
+ }
46
+ }, [open]);
47
+
48
+ if (!shouldRender) return null;
49
+
50
+ const handleBackdropClick = (e: React.MouseEvent) => {
51
+ if (e.target === e.currentTarget && onClose) {
52
+ onClose();
53
+ }
54
+ };
28
55
 
29
56
  return (
30
57
  <div
@@ -33,8 +60,11 @@ export const Modal = forwardRef<HTMLDivElement, ModalProps>(
33
60
  >
34
61
  {/* Backdrop */}
35
62
  <div
36
- className="fixed inset-0 bg-background/80 backdrop-blur-sm"
37
- onClick={onClose}
63
+ className={`fixed inset-0 bg-background/80 backdrop-blur-sm transition-opacity duration-200 ${
64
+ isAnimating ? 'opacity-100' : 'opacity-0'
65
+ }`}
66
+ onClick={handleBackdropClick}
67
+ aria-hidden="true"
38
68
  />
39
69
 
40
70
  {/* Modal */}
@@ -42,7 +72,8 @@ export const Modal = forwardRef<HTMLDivElement, ModalProps>(
42
72
  ref={ref}
43
73
  className={cn(
44
74
  'relative z-50 w-full rounded-lg border bg-card p-6 shadow-lg',
45
- 'animate-in fade-in-0 zoom-in-95',
75
+ 'transition-all duration-200',
76
+ isAnimating ? 'opacity-100 scale-100' : 'opacity-0 scale-95',
46
77
  sizeStyles[size],
47
78
  className
48
79
  )}
@@ -53,6 +84,7 @@ export const Modal = forwardRef<HTMLDivElement, ModalProps>(
53
84
  <button
54
85
  onClick={onClose}
55
86
  className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
87
+ aria-label="Close modal"
56
88
  >
57
89
  <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
58
90
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -66,7 +98,7 @@ export const Modal = forwardRef<HTMLDivElement, ModalProps>(
66
98
  </div>
67
99
  );
68
100
  }
69
- );
101
+ ));
70
102
 
71
103
  Modal.displayName = 'Modal';
72
104
 
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Navbar Component (Organism)
3
- * @description Navigation bar with logo and links
3
+ * @description Navigation bar with logo and links - optimized for performance
4
4
  */
5
5
 
6
6
  import { forwardRef, type HTMLAttributes } from 'react';
7
+ import React from 'react';
7
8
  import { cn } from '../../infrastructure/utils';
8
9
  import type { BaseProps } from '../../domain/types';
9
10
 
@@ -17,7 +18,7 @@ const variantStyles: Record<'default' | 'sticky' | 'fixed', string> = {
17
18
  fixed: 'fixed top-0 left-0 right-0 z-50',
18
19
  };
19
20
 
20
- export const Navbar = forwardRef<HTMLElement, NavbarProps>(
21
+ export const Navbar = React.memo(forwardRef<HTMLElement, NavbarProps>(
21
22
  ({ className, variant = 'default', children, ...props }, ref) => {
22
23
  return (
23
24
  <nav
@@ -33,11 +34,11 @@ export const Navbar = forwardRef<HTMLElement, NavbarProps>(
33
34
  </nav>
34
35
  );
35
36
  }
36
- );
37
+ ));
37
38
 
38
39
  Navbar.displayName = 'Navbar';
39
40
 
40
- export const NavbarBrand = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
41
+ export const NavbarBrand = React.memo(forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
41
42
  ({ className, ...props }, ref) => (
42
43
  <div
43
44
  ref={ref}
@@ -45,11 +46,11 @@ export const NavbarBrand = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElem
45
46
  {...props}
46
47
  />
47
48
  )
48
- );
49
+ ));
49
50
 
50
51
  NavbarBrand.displayName = 'NavbarBrand';
51
52
 
52
- export const NavbarLinks = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
53
+ export const NavbarLinks = React.memo(forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
53
54
  ({ className, ...props }, ref) => (
54
55
  <div
55
56
  ref={ref}
@@ -57,11 +58,11 @@ export const NavbarLinks = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElem
57
58
  {...props}
58
59
  />
59
60
  )
60
- );
61
+ ));
61
62
 
62
63
  NavbarLinks.displayName = 'NavbarLinks';
63
64
 
64
- export const NavbarActions = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
65
+ export const NavbarActions = React.memo(forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
65
66
  ({ className, ...props }, ref) => (
66
67
  <div
67
68
  ref={ref}
@@ -69,6 +70,6 @@ export const NavbarActions = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEl
69
70
  {...props}
70
71
  />
71
72
  )
72
- );
73
+ ));
73
74
 
74
75
  NavbarActions.displayName = 'NavbarActions';
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { forwardRef, type HTMLAttributes } from 'react';
7
+ import React from 'react';
7
8
  import { cn } from '../../infrastructure/utils';
8
9
  import type { BaseProps } from '../../domain/types';
9
10
 
@@ -62,7 +63,7 @@ export const TableFooter = forwardRef<HTMLTableSectionElement, HTMLAttributes<HT
62
63
 
63
64
  TableFooter.displayName = 'TableFooter';
64
65
 
65
- export const TableRow = forwardRef<HTMLTableRowElement, HTMLAttributes<HTMLTableRowElement>>(
66
+ export const TableRow = React.memo(forwardRef<HTMLTableRowElement, HTMLAttributes<HTMLTableRowElement>>(
66
67
  ({ className, ...props }, ref) => (
67
68
  <tr
68
69
  ref={ref}
@@ -73,11 +74,11 @@ export const TableRow = forwardRef<HTMLTableRowElement, HTMLAttributes<HTMLTable
73
74
  {...props}
74
75
  />
75
76
  )
76
- );
77
+ ));
77
78
 
78
79
  TableRow.displayName = 'TableRow';
79
80
 
80
- export const TableHead = forwardRef<HTMLTableCellElement, HTMLAttributes<HTMLTableCellElement>>(
81
+ export const TableHead = React.memo(forwardRef<HTMLTableCellElement, HTMLAttributes<HTMLTableCellElement>>(
81
82
  ({ className, ...props }, ref) => (
82
83
  <th
83
84
  ref={ref}
@@ -88,11 +89,11 @@ export const TableHead = forwardRef<HTMLTableCellElement, HTMLAttributes<HTMLTab
88
89
  {...props}
89
90
  />
90
91
  )
91
- );
92
+ ));
92
93
 
93
94
  TableHead.displayName = 'TableHead';
94
95
 
95
- export const TableCell = forwardRef<HTMLTableCellElement, HTMLAttributes<HTMLTableCellElement> & { colSpan?: number }>(
96
+ export const TableCell = React.memo(forwardRef<HTMLTableCellElement, HTMLAttributes<HTMLTableCellElement> & { colSpan?: number }>(
96
97
  ({ className, ...props }, ref) => (
97
98
  <td
98
99
  ref={ref}
@@ -103,7 +104,7 @@ export const TableCell = forwardRef<HTMLTableCellElement, HTMLAttributes<HTMLTab
103
104
  {...props}
104
105
  />
105
106
  )
106
- );
107
+ ));
107
108
 
108
109
  TableCell.displayName = 'TableCell';
109
110