@umituz/web-design-system 1.0.0
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/LICENSE +21 -0
- package/README.md +235 -0
- package/package.json +64 -0
- package/src/domain/tokens/color.tokens.ts +95 -0
- package/src/domain/tokens/index.ts +44 -0
- package/src/domain/tokens/radius.tokens.ts +27 -0
- package/src/domain/tokens/shadow.tokens.ts +25 -0
- package/src/domain/tokens/spacing.tokens.ts +65 -0
- package/src/domain/tokens/typography.tokens.ts +74 -0
- package/src/domain/types/component.types.ts +23 -0
- package/src/domain/types/index.ts +14 -0
- package/src/index.ts +28 -0
- package/src/infrastructure/constants/component.constants.ts +24 -0
- package/src/infrastructure/constants/index.ts +13 -0
- package/src/infrastructure/utils/cn.util.ts +13 -0
- package/src/infrastructure/utils/index.ts +10 -0
- package/src/presentation/atoms/Badge.tsx +46 -0
- package/src/presentation/atoms/Button.tsx +54 -0
- package/src/presentation/atoms/Icon.tsx +37 -0
- package/src/presentation/atoms/Input.tsx +44 -0
- package/src/presentation/atoms/Spinner.tsx +39 -0
- package/src/presentation/atoms/Text.tsx +64 -0
- package/src/presentation/atoms/index.ts +23 -0
- package/src/presentation/hooks/index.ts +13 -0
- package/src/presentation/hooks/useLocalStorage.ts +44 -0
- package/src/presentation/hooks/useMediaQuery.ts +46 -0
- package/src/presentation/hooks/useTheme.ts +51 -0
- package/src/presentation/molecules/Avatar.tsx +52 -0
- package/src/presentation/molecules/Chip.tsx +58 -0
- package/src/presentation/molecules/FormField.tsx +61 -0
- package/src/presentation/molecules/SearchBox.tsx +45 -0
- package/src/presentation/molecules/Toggle.tsx +61 -0
- package/src/presentation/molecules/index.ts +20 -0
- package/src/presentation/organisms/Alert.tsx +96 -0
- package/src/presentation/organisms/Card.tsx +90 -0
- package/src/presentation/organisms/Modal.tsx +130 -0
- package/src/presentation/organisms/Navbar.tsx +74 -0
- package/src/presentation/organisms/index.ts +17 -0
- package/src/presentation/templates/Form.tsx +41 -0
- package/src/presentation/templates/List.tsx +46 -0
- package/src/presentation/templates/Section.tsx +39 -0
- package/src/presentation/templates/index.ts +14 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FormField Component (Molecule)
|
|
3
|
+
* @description Label + Input combination
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { forwardRef, type ReactNode } from 'react';
|
|
7
|
+
import { cn } from '../../infrastructure/utils';
|
|
8
|
+
import type { BaseProps } from '../../domain/types';
|
|
9
|
+
import { Input } from '../atoms/Input';
|
|
10
|
+
import { Text } from '../atoms/Text';
|
|
11
|
+
|
|
12
|
+
export interface FormFieldProps extends BaseProps {
|
|
13
|
+
label?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
helperText?: string;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
id?: string;
|
|
18
|
+
inputProps?: React.ComponentProps<typeof Input>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const FormField = forwardRef<HTMLInputElement, FormFieldProps>(
|
|
22
|
+
({ label, error, helperText, required, id, className, inputProps, ...props }, ref) => {
|
|
23
|
+
const fieldId = id || inputProps?.id || inputProps?.name;
|
|
24
|
+
const errorId = error ? `${fieldId}-error` : undefined;
|
|
25
|
+
const helperId = helperText ? `${fieldId}-helper` : undefined;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className={cn('space-y-1.5', className)} {...props}>
|
|
29
|
+
{label && (
|
|
30
|
+
<Text as="label" variant="label" size="sm" htmlFor={fieldId}>
|
|
31
|
+
{label}
|
|
32
|
+
{required && <span className="text-destructive ml-1">*</span>}
|
|
33
|
+
</Text>
|
|
34
|
+
)}
|
|
35
|
+
|
|
36
|
+
<Input
|
|
37
|
+
ref={ref}
|
|
38
|
+
id={fieldId}
|
|
39
|
+
aria-invalid={!!error}
|
|
40
|
+
aria-describedby={cn(errorId, helperId)}
|
|
41
|
+
error={!!error}
|
|
42
|
+
{...inputProps}
|
|
43
|
+
/>
|
|
44
|
+
|
|
45
|
+
{error && (
|
|
46
|
+
<Text as="p" variant="caption" size="sm" id={errorId} className="text-destructive">
|
|
47
|
+
{error}
|
|
48
|
+
</Text>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{helperText && !error && (
|
|
52
|
+
<Text as="p" variant="caption" size="sm" id={helperId} className="text-muted-foreground">
|
|
53
|
+
{helperText}
|
|
54
|
+
</Text>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
FormField.displayName = 'FormField';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SearchBox Component (Molecule)
|
|
3
|
+
* @description Input with search icon
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { forwardRef } from 'react';
|
|
7
|
+
import { cn } from '../../infrastructure/utils';
|
|
8
|
+
import type { BaseProps } from '../../domain/types';
|
|
9
|
+
import { Input } from '../atoms/Input';
|
|
10
|
+
import { Icon } from '../atoms/Icon';
|
|
11
|
+
|
|
12
|
+
export interface SearchBoxProps extends BaseProps {
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
value?: string;
|
|
15
|
+
onChange?: (value: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const SearchBox = forwardRef<HTMLInputElement, SearchBoxProps>(
|
|
19
|
+
({ placeholder = 'Search...', value, onChange, className, ...props }, ref) => {
|
|
20
|
+
return (
|
|
21
|
+
<div className={cn('relative', className)} {...props}>
|
|
22
|
+
<Icon
|
|
23
|
+
className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
|
|
24
|
+
size="sm"
|
|
25
|
+
>
|
|
26
|
+
<path
|
|
27
|
+
strokeLinecap="round"
|
|
28
|
+
strokeLinejoin="round"
|
|
29
|
+
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
|
|
30
|
+
/>
|
|
31
|
+
</Icon>
|
|
32
|
+
<Input
|
|
33
|
+
ref={ref}
|
|
34
|
+
type="text"
|
|
35
|
+
placeholder={placeholder}
|
|
36
|
+
value={value}
|
|
37
|
+
onChange={(e) => onChange?.(e.target.value)}
|
|
38
|
+
className="pl-9"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
SearchBox.displayName = 'SearchBox';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toggle Component (Molecule)
|
|
3
|
+
* @description Switch/toggle button
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { forwardRef, type ButtonHTMLAttributes } from 'react';
|
|
7
|
+
import { cn } from '../../infrastructure/utils';
|
|
8
|
+
import type { BaseProps } from '../../domain/types';
|
|
9
|
+
|
|
10
|
+
export interface ToggleProps extends ButtonHTMLAttributes<HTMLButtonElement>, BaseProps {
|
|
11
|
+
pressed?: boolean;
|
|
12
|
+
onPressedChange?: (pressed: boolean) => void;
|
|
13
|
+
size?: 'sm' | 'md' | 'lg';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sizeStyles = {
|
|
17
|
+
sm: 'h-5 w-9',
|
|
18
|
+
md: 'h-6 w-11',
|
|
19
|
+
lg: 'h-7 w-13',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const thumbSizeStyles = {
|
|
23
|
+
sm: 'h-4 w-4 data-[state=checked:translate-x-4',
|
|
24
|
+
md: 'h-5 w-5 data-[state=checked:translate-x-5',
|
|
25
|
+
lg: 'h-6 w-6 data-[state=checked:translate-x-6',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const Toggle = forwardRef<HTMLButtonElement, ToggleProps>(
|
|
29
|
+
({ className, pressed = false, onPressedChange, size = 'md', disabled, ...props }, ref) => {
|
|
30
|
+
return (
|
|
31
|
+
<button
|
|
32
|
+
ref={ref}
|
|
33
|
+
type="button"
|
|
34
|
+
role="switch"
|
|
35
|
+
aria-checked={pressed}
|
|
36
|
+
disabled={disabled}
|
|
37
|
+
data-state={pressed ? 'checked' : 'unchecked'}
|
|
38
|
+
onClick={() => onPressedChange?.(!pressed)}
|
|
39
|
+
className={cn(
|
|
40
|
+
'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent',
|
|
41
|
+
'transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
42
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
43
|
+
'data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
|
44
|
+
sizeStyles[size],
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
>
|
|
49
|
+
<span
|
|
50
|
+
className={cn(
|
|
51
|
+
'pointer-events-none block rounded-full bg-background shadow-lg ring-0 transition-transform',
|
|
52
|
+
thumbSizeStyles[size],
|
|
53
|
+
'data-[state=unchecked]:translate-x-0'
|
|
54
|
+
)}
|
|
55
|
+
/>
|
|
56
|
+
</button>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
Toggle.displayName = 'Toggle';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Molecules Export
|
|
3
|
+
* @description Molecule components - combinations of atoms
|
|
4
|
+
* Subpath: @umituz/web-design-system/molecules
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { FormField } from './FormField';
|
|
8
|
+
export type { FormFieldProps } from './FormField';
|
|
9
|
+
|
|
10
|
+
export { SearchBox } from './SearchBox';
|
|
11
|
+
export type { SearchBoxProps } from './SearchBox';
|
|
12
|
+
|
|
13
|
+
export { Avatar } from './Avatar';
|
|
14
|
+
export type { AvatarProps } from './Avatar';
|
|
15
|
+
|
|
16
|
+
export { Chip } from './Chip';
|
|
17
|
+
export type { ChipProps } from './Chip';
|
|
18
|
+
|
|
19
|
+
export { Toggle } from './Toggle';
|
|
20
|
+
export type { ToggleProps } from './Toggle';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert Component (Organism)
|
|
3
|
+
* @description Feedback message with icon
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { forwardRef, type HTMLAttributes } from 'react';
|
|
7
|
+
import { cn } from '../../infrastructure/utils';
|
|
8
|
+
import type { BaseProps, ChildrenProps, ColorVariant } from '../../domain/types';
|
|
9
|
+
import { Icon } from '../atoms/Icon';
|
|
10
|
+
|
|
11
|
+
export type AlertVariant = Extract<ColorVariant, 'success' | 'warning' | 'destructive'> | 'info';
|
|
12
|
+
|
|
13
|
+
export interface AlertProps extends HTMLAttributes<HTMLDivElement>, BaseProps, ChildrenProps {
|
|
14
|
+
variant?: AlertVariant;
|
|
15
|
+
showIcon?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const variantStyles: Record<AlertVariant, { container: string; iconColor: string }> = {
|
|
19
|
+
success: {
|
|
20
|
+
container: 'border-success bg-success/10 text-success',
|
|
21
|
+
iconColor: 'text-success',
|
|
22
|
+
},
|
|
23
|
+
warning: {
|
|
24
|
+
container: 'border-warning bg-warning/10 text-warning',
|
|
25
|
+
iconColor: 'text-warning',
|
|
26
|
+
},
|
|
27
|
+
destructive: {
|
|
28
|
+
container: 'border-destructive bg-destructive/10 text-destructive',
|
|
29
|
+
iconColor: 'text-destructive',
|
|
30
|
+
},
|
|
31
|
+
info: {
|
|
32
|
+
container: 'border-primary bg-primary/10 text-primary',
|
|
33
|
+
iconColor: 'text-primary',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const icons: Record<AlertVariant, React.ReactNode> = {
|
|
38
|
+
success: (
|
|
39
|
+
<path
|
|
40
|
+
strokeLinecap="round"
|
|
41
|
+
strokeLinejoin="round"
|
|
42
|
+
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
43
|
+
/>
|
|
44
|
+
),
|
|
45
|
+
warning: (
|
|
46
|
+
<path
|
|
47
|
+
strokeLinecap="round"
|
|
48
|
+
strokeLinejoin="round"
|
|
49
|
+
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
|
|
50
|
+
/>
|
|
51
|
+
),
|
|
52
|
+
destructive: (
|
|
53
|
+
<path
|
|
54
|
+
strokeLinecap="round"
|
|
55
|
+
strokeLinejoin="round"
|
|
56
|
+
d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"
|
|
57
|
+
/>
|
|
58
|
+
),
|
|
59
|
+
info: (
|
|
60
|
+
<path
|
|
61
|
+
strokeLinecap="round"
|
|
62
|
+
strokeLinejoin="round"
|
|
63
|
+
d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
|
|
64
|
+
/>
|
|
65
|
+
),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const Alert = forwardRef<HTMLDivElement, AlertProps>(
|
|
69
|
+
({ className, variant = 'info', showIcon = true, children, ...props }, ref) => {
|
|
70
|
+
const styles = variantStyles[variant];
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
ref={ref}
|
|
75
|
+
role="alert"
|
|
76
|
+
className={cn(
|
|
77
|
+
'relative w-full rounded-lg border p-4',
|
|
78
|
+
styles.container,
|
|
79
|
+
className
|
|
80
|
+
)}
|
|
81
|
+
{...props}
|
|
82
|
+
>
|
|
83
|
+
<div className="flex items-start gap-3">
|
|
84
|
+
{showIcon && (
|
|
85
|
+
<Icon className={cn('shrink-0 mt-0.5', styles.iconColor)} size="sm">
|
|
86
|
+
{icons[variant]}
|
|
87
|
+
</Icon>
|
|
88
|
+
)}
|
|
89
|
+
<div className="flex-1">{children}</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
Alert.displayName = 'Alert';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card Component (Organism)
|
|
3
|
+
* @description Content container with header, body, footer
|
|
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 interface CardProps extends HTMLAttributes<HTMLDivElement>, BaseProps, ChildrenProps {
|
|
11
|
+
variant?: 'default' | 'outlined' | 'elevated';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const variantStyles: Record<'default' | 'outlined' | 'elevated', string> = {
|
|
15
|
+
default: 'border border-border bg-card text-card-foreground',
|
|
16
|
+
outlined: 'border-2 border-border bg-transparent',
|
|
17
|
+
elevated: 'border-0 shadow-lg bg-card text-card-foreground',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const Card = forwardRef<HTMLDivElement, CardProps>(
|
|
21
|
+
({ className, variant = 'default', children, ...props }, ref) => {
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn('rounded-lg', variantStyles[variant], className)}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
Card.displayName = 'Card';
|
|
35
|
+
|
|
36
|
+
export const CardHeader = forwardRef<HTMLDivElement, CardProps>(
|
|
37
|
+
({ className, ...props }, ref) => (
|
|
38
|
+
<div
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CardHeader.displayName = 'CardHeader';
|
|
47
|
+
|
|
48
|
+
export const CardTitle = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLHeadingElement>>(
|
|
49
|
+
({ className, ...props }, ref) => (
|
|
50
|
+
<h3
|
|
51
|
+
ref={ref}
|
|
52
|
+
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
CardTitle.displayName = 'CardTitle';
|
|
59
|
+
|
|
60
|
+
export const CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
|
|
61
|
+
({ className, ...props }, ref) => (
|
|
62
|
+
<p
|
|
63
|
+
ref={ref}
|
|
64
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
CardDescription.displayName = 'CardDescription';
|
|
71
|
+
|
|
72
|
+
export const CardContent = forwardRef<HTMLDivElement, CardProps>(
|
|
73
|
+
({ className, ...props }, ref) => (
|
|
74
|
+
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
CardContent.displayName = 'CardContent';
|
|
79
|
+
|
|
80
|
+
export const CardFooter = forwardRef<HTMLDivElement, CardProps>(
|
|
81
|
+
({ className, ...props }, ref) => (
|
|
82
|
+
<div
|
|
83
|
+
ref={ref}
|
|
84
|
+
className={cn('flex items-center p-6 pt-0', className)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
CardFooter.displayName = 'CardFooter';
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modal Component (Organism)
|
|
3
|
+
* @description Dialog/overlay container
|
|
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 ModalProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
|
|
11
|
+
open?: boolean;
|
|
12
|
+
onClose?: () => void;
|
|
13
|
+
showCloseButton?: boolean;
|
|
14
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const sizeStyles: Record<'sm' | 'md' | 'lg' | 'xl' | 'full', string> = {
|
|
18
|
+
sm: 'max-w-sm',
|
|
19
|
+
md: 'max-w-md',
|
|
20
|
+
lg: 'max-w-lg',
|
|
21
|
+
xl: 'max-w-xl',
|
|
22
|
+
full: 'max-w-full mx-4',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Modal = forwardRef<HTMLDivElement, ModalProps>(
|
|
26
|
+
({ open = false, onClose, showCloseButton = true, size = 'md', className, children, ...props }, ref) => {
|
|
27
|
+
if (!open) return null;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
className="fixed inset-0 z-50 flex items-center justify-center"
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
{/* Backdrop */}
|
|
35
|
+
<div
|
|
36
|
+
className="fixed inset-0 bg-background/80 backdrop-blur-sm"
|
|
37
|
+
onClick={onClose}
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
{/* Modal */}
|
|
41
|
+
<div
|
|
42
|
+
ref={ref}
|
|
43
|
+
className={cn(
|
|
44
|
+
'relative z-50 w-full rounded-lg border bg-card p-6 shadow-lg',
|
|
45
|
+
'animate-in fade-in-0 zoom-in-95',
|
|
46
|
+
sizeStyles[size],
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
role="dialog"
|
|
50
|
+
aria-modal="true"
|
|
51
|
+
>
|
|
52
|
+
{showCloseButton && (
|
|
53
|
+
<button
|
|
54
|
+
onClick={onClose}
|
|
55
|
+
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"
|
|
56
|
+
>
|
|
57
|
+
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
58
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
59
|
+
</svg>
|
|
60
|
+
<span className="sr-only">Close</span>
|
|
61
|
+
</button>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
{children}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
Modal.displayName = 'Modal';
|
|
72
|
+
|
|
73
|
+
export const ModalHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
74
|
+
({ className, ...props }, ref) => (
|
|
75
|
+
<div
|
|
76
|
+
ref={ref}
|
|
77
|
+
className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
ModalHeader.displayName = 'ModalHeader';
|
|
84
|
+
|
|
85
|
+
export const ModalTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingElement>>(
|
|
86
|
+
({ className, ...props }, ref) => (
|
|
87
|
+
<h2
|
|
88
|
+
ref={ref}
|
|
89
|
+
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
ModalTitle.displayName = 'ModalTitle';
|
|
96
|
+
|
|
97
|
+
export const ModalDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
|
|
98
|
+
({ className, ...props }, ref) => (
|
|
99
|
+
<p
|
|
100
|
+
ref={ref}
|
|
101
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
ModalDescription.displayName = 'ModalDescription';
|
|
108
|
+
|
|
109
|
+
export const ModalContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
110
|
+
({ className, ...props }, ref) => (
|
|
111
|
+
<div ref={ref} className={cn('mt-4', className)} {...props} />
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
ModalContent.displayName = 'ModalContent';
|
|
116
|
+
|
|
117
|
+
export const ModalFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
118
|
+
({ className, ...props }, ref) => (
|
|
119
|
+
<div
|
|
120
|
+
ref={ref}
|
|
121
|
+
className={cn(
|
|
122
|
+
'mt-4 flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
|
123
|
+
className
|
|
124
|
+
)}
|
|
125
|
+
{...props}
|
|
126
|
+
/>
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
ModalFooter.displayName = 'ModalFooter';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navbar Component (Organism)
|
|
3
|
+
* @description Navigation bar with logo and links
|
|
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 interface NavbarProps extends HTMLAttributes<HTMLElement>, BaseProps, ChildrenProps {
|
|
11
|
+
variant?: 'default' | 'sticky' | 'fixed';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const variantStyles: Record<'default' | 'sticky' | 'fixed', string> = {
|
|
15
|
+
default: 'relative',
|
|
16
|
+
sticky: 'sticky top-0 z-50',
|
|
17
|
+
fixed: 'fixed top-0 left-0 right-0 z-50',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const Navbar = forwardRef<HTMLElement, NavbarProps>(
|
|
21
|
+
({ className, variant = 'default', children, ...props }, ref) => {
|
|
22
|
+
return (
|
|
23
|
+
<nav
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn(
|
|
26
|
+
'flex h-16 items-center gap-4 border-b border-border/40 bg-background/95 px-4 backdrop-blur supports-[backdrop-filter]:bg-background/60',
|
|
27
|
+
variantStyles[variant],
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</nav>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
Navbar.displayName = 'Navbar';
|
|
39
|
+
|
|
40
|
+
export const NavbarBrand = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
41
|
+
({ className, ...props }, ref) => (
|
|
42
|
+
<div
|
|
43
|
+
ref={ref}
|
|
44
|
+
className={cn('flex items-center gap-2 font-bold text-lg', className)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
NavbarBrand.displayName = 'NavbarBrand';
|
|
51
|
+
|
|
52
|
+
export const NavbarLinks = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
53
|
+
({ className, ...props }, ref) => (
|
|
54
|
+
<div
|
|
55
|
+
ref={ref}
|
|
56
|
+
className={cn('hidden items-center gap-6 md:flex', className)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
NavbarLinks.displayName = 'NavbarLinks';
|
|
63
|
+
|
|
64
|
+
export const NavbarActions = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
65
|
+
({ className, ...props }, ref) => (
|
|
66
|
+
<div
|
|
67
|
+
ref={ref}
|
|
68
|
+
className={cn('ml-auto flex items-center gap-2', className)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
NavbarActions.displayName = 'NavbarActions';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Organisms Export
|
|
3
|
+
* @description Organism components - complex UI combinations
|
|
4
|
+
* Subpath: @umituz/web-design-system/organisms
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './Card';
|
|
8
|
+
export type { CardProps } from './Card';
|
|
9
|
+
|
|
10
|
+
export { Alert } from './Alert';
|
|
11
|
+
export type { AlertProps, AlertVariant } from './Alert';
|
|
12
|
+
|
|
13
|
+
export { Modal, ModalHeader, ModalTitle, ModalDescription, ModalContent, ModalFooter } from './Modal';
|
|
14
|
+
export type { ModalProps } from './Modal';
|
|
15
|
+
|
|
16
|
+
export { Navbar, NavbarBrand, NavbarLinks, NavbarActions } from './Navbar';
|
|
17
|
+
export type { NavbarProps } from './Navbar';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form Template (Template)
|
|
3
|
+
* @description Complete form structure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { forwardRef, type FormHTMLAttributes } from 'react';
|
|
7
|
+
import { cn } from '../../infrastructure/utils';
|
|
8
|
+
import type { BaseProps, ChildrenProps } from '../../domain/types';
|
|
9
|
+
|
|
10
|
+
export interface FormProps extends FormHTMLAttributes<HTMLFormElement>, BaseProps, ChildrenProps {
|
|
11
|
+
onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const Form = forwardRef<HTMLFormElement, FormProps>(
|
|
15
|
+
({ className, onSubmit, children, ...props }, ref) => {
|
|
16
|
+
return (
|
|
17
|
+
<form
|
|
18
|
+
ref={ref}
|
|
19
|
+
onSubmit={onSubmit}
|
|
20
|
+
className={cn('space-y-4', className)}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</form>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
Form.displayName = 'Form';
|
|
30
|
+
|
|
31
|
+
export const FormActions = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
32
|
+
({ className, ...props }, ref) => (
|
|
33
|
+
<div
|
|
34
|
+
ref={ref}
|
|
35
|
+
className={cn('flex items-center gap-2 justify-end', className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
FormActions.displayName = 'FormActions';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Template (Template)
|
|
3
|
+
* @description Structured list container
|
|
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 interface ListProps extends HTMLAttributes<HTMLUListElement>, BaseProps, ChildrenProps {
|
|
11
|
+
variant?: 'default' | 'bordered' | 'spaced';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const variantStyles: Record<'default' | 'bordered' | 'spaced', string> = {
|
|
15
|
+
default: '',
|
|
16
|
+
bordered: 'divide-y divide-border',
|
|
17
|
+
spaced: 'space-y-2',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const List = forwardRef<HTMLUListElement, ListProps>(
|
|
21
|
+
({ className, variant = 'default', children, ...props }, ref) => {
|
|
22
|
+
return (
|
|
23
|
+
<ul
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn(variantStyles[variant], className)}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</ul>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
List.displayName = 'List';
|
|
35
|
+
|
|
36
|
+
export const ListItem = forwardRef<HTMLLIElement, HTMLAttributes<HTMLLIElement>>(
|
|
37
|
+
({ className, ...props }, ref) => (
|
|
38
|
+
<li
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={cn('py-2', className)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
ListItem.displayName = 'ListItem';
|