@umituz/web-design-system 1.1.0 → 1.3.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 (30) hide show
  1. package/package.json +8 -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/CheckboxGroup.tsx +1 -1
  15. package/src/presentation/molecules/FormField.tsx +2 -2
  16. package/src/presentation/molecules/InputGroup.tsx +1 -1
  17. package/src/presentation/molecules/RadioGroup.tsx +1 -1
  18. package/src/presentation/organisms/Alert.tsx +1 -1
  19. package/src/presentation/organisms/Card.tsx +1 -1
  20. package/src/presentation/organisms/Footer.tsx +99 -0
  21. package/src/presentation/organisms/Navbar.tsx +1 -1
  22. package/src/presentation/organisms/Table.tsx +1 -1
  23. package/src/presentation/organisms/index.ts +3 -0
  24. package/src/presentation/templates/Form.tsx +2 -2
  25. package/src/presentation/templates/List.tsx +1 -1
  26. package/src/presentation/templates/PageHeader.tsx +78 -0
  27. package/src/presentation/templates/PageLayout.tsx +38 -0
  28. package/src/presentation/templates/ProjectSkeleton.tsx +35 -0
  29. package/src/presentation/templates/Section.tsx +1 -1
  30. 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.3.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,
@@ -52,13 +53,17 @@
52
53
  },
53
54
  "peerDependencies": {
54
55
  "react": ">=18.0.0",
55
- "react-dom": ">=18.0.0"
56
+ "react-dom": ">=18.0.0",
57
+ "clsx": ">=2.0.0",
58
+ "tailwind-merge": ">=2.0.0"
56
59
  },
57
60
  "devDependencies": {
58
61
  "@types/react": "^18.0.0",
59
62
  "@types/react-dom": "^18.0.0",
63
+ "clsx": "^2.1.1",
60
64
  "react": "^18.0.0",
61
65
  "react-dom": "^18.0.0",
66
+ "tailwind-merge": "^3.5.0",
62
67
  "typescript": "~5.9.2"
63
68
  },
64
69
  "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
  }
@@ -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;
@@ -10,7 +10,7 @@ import { Icon } from '../atoms/Icon';
10
10
 
11
11
  export type AlertVariant = Extract<ColorVariant, 'success' | 'warning' | 'destructive'> | 'info';
12
12
 
13
- export interface AlertProps extends HTMLAttributes<HTMLDivElement>, BaseProps, ChildrenProps {
13
+ export interface AlertProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
14
14
  variant?: AlertVariant;
15
15
  showIcon?: boolean;
16
16
  }
@@ -7,7 +7,7 @@ import { forwardRef, type HTMLAttributes } from 'react';
7
7
  import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps, ChildrenProps } from '../../domain/types';
9
9
 
10
- export interface CardProps extends HTMLAttributes<HTMLDivElement>, BaseProps, ChildrenProps {
10
+ export interface CardProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
11
11
  variant?: 'default' | 'outlined' | 'elevated';
12
12
  }
13
13
 
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Footer Organism Component
3
+ * @description Footer with brand info, links, and social icons
4
+ */
5
+
6
+ import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';
7
+ import { cn } from '../../infrastructure/utils';
8
+ import type { BaseProps, ChildrenProps } from '../../domain/types';
9
+
10
+ export interface FooterProps extends HTMLAttributes<HTMLElement>, BaseProps {
11
+ brand?: {
12
+ name: string;
13
+ description?: string;
14
+ };
15
+ sections?: Array<{
16
+ title: string;
17
+ links: Array<{ label: string; href: string }>;
18
+ }>;
19
+ social?: Array<{
20
+ name: string;
21
+ href: string;
22
+ icon: ReactNode;
23
+ }>;
24
+ copyright?: string;
25
+ }
26
+
27
+ export const Footer = forwardRef<HTMLElement, FooterProps>(
28
+ ({ className, brand, sections, social, copyright = '© 2026 UmitUZ. All rights reserved.', ...props }, ref) => {
29
+ return (
30
+ <footer
31
+ ref={ref}
32
+ className={cn('bg-bg-primary border-t border-border mt-20 transition-theme', className)}
33
+ {...props}
34
+ >
35
+ <div className="max-w-7xl mx-auto px-4 py-12">
36
+ <div className="grid md:grid-cols-3 gap-8">
37
+ {/* Brand */}
38
+ {brand && (
39
+ <div>
40
+ <h3 className="text-xl font-bold text-text-primary mb-3">{brand.name}</h3>
41
+ {brand.description && (
42
+ <p className="text-text-secondary text-sm">{brand.description}</p>
43
+ )}
44
+ </div>
45
+ )}
46
+
47
+ {/* Sections */}
48
+ {sections?.map((section, index) => (
49
+ <div key={index}>
50
+ <h4 className="font-semibold text-text-primary mb-3">{section.title}</h4>
51
+ <ul className="space-y-2 text-sm">
52
+ {section.links.map((link, linkIndex) => (
53
+ <li key={linkIndex}>
54
+ <a
55
+ href={link.href}
56
+ className="text-text-secondary hover:text-primary-light transition-colors"
57
+ >
58
+ {link.label}
59
+ </a>
60
+ </li>
61
+ ))}
62
+ </ul>
63
+ </div>
64
+ ))}
65
+
66
+ {/* Social */}
67
+ {social && (
68
+ <div>
69
+ <h4 className="font-semibold text-text-primary mb-3">Connect</h4>
70
+ <div className="flex gap-4">
71
+ {social.map((item, index) => (
72
+ <a
73
+ key={index}
74
+ href={item.href}
75
+ target="_blank"
76
+ rel="noopener noreferrer"
77
+ className="text-text-secondary hover:text-primary-light transition-colors"
78
+ >
79
+ {item.icon}
80
+ </a>
81
+ ))}
82
+ </div>
83
+ </div>
84
+ )}
85
+ </div>
86
+
87
+ {/* Copyright */}
88
+ {copyright && (
89
+ <div className="border-t border-border mt-8 pt-8 text-center text-sm text-text-secondary">
90
+ <p>{copyright}</p>
91
+ </div>
92
+ )}
93
+ </div>
94
+ </footer>
95
+ );
96
+ }
97
+ );
98
+
99
+ Footer.displayName = 'Footer';
@@ -7,7 +7,7 @@ import { forwardRef, type HTMLAttributes } from 'react';
7
7
  import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps, ChildrenProps } from '../../domain/types';
9
9
 
10
- export interface NavbarProps extends HTMLAttributes<HTMLElement>, BaseProps, ChildrenProps {
10
+ export interface NavbarProps extends HTMLAttributes<HTMLElement>, BaseProps {
11
11
  variant?: 'default' | 'sticky' | 'fixed';
12
12
  }
13
13
 
@@ -7,7 +7,7 @@ import { forwardRef, type HTMLAttributes } from 'react';
7
7
  import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps, ChildrenProps } from '../../domain/types';
9
9
 
10
- export interface TableProps extends HTMLAttributes<HTMLTableElement>, BaseProps, ChildrenProps {}
10
+ export interface TableProps extends HTMLAttributes<HTMLTableElement>, BaseProps {}
11
11
 
12
12
  export const Table = forwardRef<HTMLTableElement, TableProps>(
13
13
  ({ className, children, ...props }, ref) => {
@@ -36,3 +36,6 @@ export type { AccordionProps, AccordionItem } from './Accordion';
36
36
 
37
37
  export { Breadcrumbs } from './Breadcrumb';
38
38
  export type { BreadcrumbsProps, BreadcrumbItem } from './Breadcrumb';
39
+
40
+ export { Footer } from './Footer';
41
+ export type { FooterProps } from './Footer';
@@ -3,11 +3,11 @@
3
3
  * @description Complete form structure
4
4
  */
5
5
 
6
- import { forwardRef, type FormHTMLAttributes } from 'react';
6
+ import { forwardRef, type FormHTMLAttributes, type HTMLAttributes } from 'react';
7
7
  import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps, ChildrenProps } from '../../domain/types';
9
9
 
10
- export interface FormProps extends FormHTMLAttributes<HTMLFormElement>, BaseProps, ChildrenProps {
10
+ export interface FormProps extends FormHTMLAttributes<HTMLFormElement>, BaseProps {
11
11
  onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void;
12
12
  }
13
13
 
@@ -7,7 +7,7 @@ import { forwardRef, type HTMLAttributes } from 'react';
7
7
  import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps, ChildrenProps } from '../../domain/types';
9
9
 
10
- export interface ListProps extends HTMLAttributes<HTMLUListElement>, BaseProps, ChildrenProps {
10
+ export interface ListProps extends HTMLAttributes<HTMLUListElement>, BaseProps {
11
11
  variant?: 'default' | 'bordered' | 'spaced';
12
12
  }
13
13
 
@@ -0,0 +1,78 @@
1
+ /**
2
+ * PageHeader Template Component
3
+ * @description Centralized page header with consistent heading styling
4
+ */
5
+
6
+ import { forwardRef, type HTMLAttributes } from 'react';
7
+ import { type ReactNode } from 'react';
8
+ import { cn } from '../../infrastructure/utils';
9
+ import type { BaseProps } from '../../domain/types';
10
+
11
+ export type TextAlign = 'left' | 'center' | 'right';
12
+ export type HeaderSize = 'small' | 'medium' | 'large';
13
+
14
+ export interface PageHeaderProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'>, BaseProps {
15
+ title: ReactNode;
16
+ subtitle?: string;
17
+ align?: TextAlign;
18
+ size?: HeaderSize;
19
+ actions?: ReactNode;
20
+ }
21
+
22
+ const sizeClasses: Record<HeaderSize, { title: string; subtitle: string }> = {
23
+ small: {
24
+ title: 'text-2xl md:text-3xl font-bold',
25
+ subtitle: 'text-base md:text-lg',
26
+ },
27
+ medium: {
28
+ title: 'text-3xl md:text-4xl font-bold',
29
+ subtitle: 'text-lg md:text-xl',
30
+ },
31
+ large: {
32
+ title: 'text-4xl md:text-5xl lg:text-6xl font-bold',
33
+ subtitle: 'text-lg md:text-xl',
34
+ },
35
+ };
36
+
37
+ const alignClasses: Record<TextAlign, string> = {
38
+ left: 'text-left',
39
+ center: 'text-center',
40
+ right: 'text-right',
41
+ };
42
+
43
+ const getAlignFlexClass = (align: TextAlign, hasActions: boolean): string => {
44
+ if (!hasActions) return '';
45
+ return align === 'center'
46
+ ? 'flex justify-center'
47
+ : align === 'right'
48
+ ? 'flex justify-end'
49
+ : 'flex justify-start';
50
+ };
51
+
52
+ export const PageHeader = forwardRef<HTMLDivElement, PageHeaderProps>(
53
+ ({ title, subtitle, align = 'center', size = 'large', actions, className, ...props }, ref) => {
54
+ return (
55
+ <div
56
+ ref={ref}
57
+ className={cn('mb-6 md:mb-12', alignClasses[align], className)}
58
+ {...props}
59
+ >
60
+ <h1 className={cn(sizeClasses[size].title, 'text-text-primary mb-4')}>
61
+ {title}
62
+ </h1>
63
+ {subtitle && (
64
+ <p className={cn(sizeClasses[size].subtitle, 'text-text-secondary')}>
65
+ {subtitle}
66
+ </p>
67
+ )}
68
+ {actions && (
69
+ <div className={cn('mt-6 md:mt-8', getAlignFlexClass(align, true))}>
70
+ {actions}
71
+ </div>
72
+ )}
73
+ </div>
74
+ );
75
+ }
76
+ );
77
+
78
+ PageHeader.displayName = 'PageHeader';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * PageLayout Template Component
3
+ * @description Centralized page layout with consistent padding, max-width, and theme integration
4
+ */
5
+
6
+ import { forwardRef, type HTMLAttributes } from 'react';
7
+ import { cn } from '../../infrastructure/utils';
8
+ import type { BaseProps, ChildrenProps } from '../../domain/types';
9
+
10
+ export type MaxWidth = '4xl' | '7xl' | 'full';
11
+
12
+ export interface PageLayoutProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
13
+ maxWidth?: MaxWidth;
14
+ }
15
+
16
+ const maxWidthClasses: Record<MaxWidth, string> = {
17
+ '4xl': 'max-w-4xl',
18
+ '7xl': 'max-w-7xl',
19
+ 'full': 'max-w-full',
20
+ };
21
+
22
+ export const PageLayout = forwardRef<HTMLDivElement, PageLayoutProps>(
23
+ ({ children, maxWidth = '7xl', className, ...props }, ref) => {
24
+ return (
25
+ <div
26
+ ref={ref}
27
+ className="min-h-screen bg-surface-gradient py-8 md:py-12 px-4 transition-theme"
28
+ {...props}
29
+ >
30
+ <div className={cn(maxWidthClasses[maxWidth], 'mx-auto', className)}>
31
+ {children}
32
+ </div>
33
+ </div>
34
+ );
35
+ }
36
+ );
37
+
38
+ PageLayout.displayName = 'PageLayout';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * ProjectSkeleton Template Component
3
+ * @description Loading skeleton for project cards
4
+ */
5
+
6
+ import { forwardRef, type HTMLAttributes } from 'react';
7
+ import { cn } from '../../infrastructure/utils';
8
+ import type { BaseProps } from '../../domain/types';
9
+
10
+ export interface ProjectSkeletonProps extends HTMLAttributes<HTMLDivElement>, BaseProps {}
11
+
12
+ export const ProjectSkeleton = forwardRef<HTMLDivElement, ProjectSkeletonProps>(
13
+ ({ className, ...props }, ref) => {
14
+ return (
15
+ <div
16
+ ref={ref}
17
+ className={cn('bg-bg-card rounded-xl overflow-hidden border border-border transition-theme', className)}
18
+ {...props}
19
+ >
20
+ <div className="h-28 bg-bg-tertiary animate-pulse" />
21
+ <div className="p-5">
22
+ <div className="flex gap-2 mb-3">
23
+ <div className="w-16 h-6 bg-bg-tertiary rounded-full animate-pulse" />
24
+ <div className="w-20 h-6 bg-bg-tertiary rounded-full animate-pulse" />
25
+ </div>
26
+ <div className="w-3/4 h-5 bg-bg-tertiary rounded mb-2 animate-pulse" />
27
+ <div className="w-full h-4 bg-bg-tertiary rounded mb-4 animate-pulse" />
28
+ <div className="w-1/2 h-4 bg-bg-tertiary rounded animate-pulse" />
29
+ </div>
30
+ </div>
31
+ );
32
+ }
33
+ );
34
+
35
+ ProjectSkeleton.displayName = 'ProjectSkeleton';
@@ -7,7 +7,7 @@ import { forwardRef, type HTMLAttributes } from 'react';
7
7
  import { cn } from '../../infrastructure/utils';
8
8
  import type { BaseProps, ChildrenProps } from '../../domain/types';
9
9
 
10
- export interface SectionProps extends HTMLAttributes<HTMLElement>, BaseProps, ChildrenProps {
10
+ export interface SectionProps extends HTMLAttributes<HTMLElement>, BaseProps {
11
11
  title?: string;
12
12
  description?: string;
13
13
  }
@@ -12,3 +12,12 @@ export type { ListProps } from './List';
12
12
 
13
13
  export { Section } from './Section';
14
14
  export type { SectionProps } from './Section';
15
+
16
+ export { PageLayout } from './PageLayout';
17
+ export type { PageLayoutProps, MaxWidth } from './PageLayout';
18
+
19
+ export { PageHeader } from './PageHeader';
20
+ export type { PageHeaderProps, TextAlign, HeaderSize } from './PageHeader';
21
+
22
+ export { ProjectSkeleton } from './ProjectSkeleton';
23
+ export type { ProjectSkeletonProps } from './ProjectSkeleton';