create-template-html-css 2.0.4 → 2.2.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/CHANGELOG.md +436 -0
- package/CODE-SPLITTING-GUIDE.md +274 -0
- package/COMPONENTS-GALLERY.html +143 -8
- package/HTML-VS-REACT.md +289 -0
- package/QUICKSTART-REACT.md +293 -0
- package/REACT-SUPPORT-SUMMARY.md +235 -0
- package/README.md +261 -12
- package/bin/cli.js +100 -759
- package/bin/commands/create.js +288 -0
- package/bin/commands/gallery.js +42 -0
- package/bin/commands/insert.js +123 -0
- package/bin/commands/list.js +73 -0
- package/package.json +10 -3
- package/src/component-choices.js +7 -0
- package/src/components-registry.js +112 -0
- package/src/format-utils.js +49 -0
- package/src/generator.js +83 -594
- package/src/generators/color-schemes.js +78 -0
- package/src/generators/color-utils.js +108 -0
- package/src/generators/component-filters.js +151 -0
- package/src/generators/html-generators.js +180 -0
- package/src/generators/validation.js +43 -0
- package/src/index.js +2 -1
- package/src/inserter.js +55 -233
- package/src/inserters/backup-utils.js +20 -0
- package/src/inserters/component-loader.js +68 -0
- package/src/inserters/html-utils.js +31 -0
- package/src/inserters/indentation-utils.js +90 -0
- package/src/inserters/validation-utils.js +49 -0
- package/src/react-component-choices.js +97 -0
- package/src/react-component-templates.js +182 -0
- package/src/react-file-operations.js +172 -0
- package/src/react-generator.js +219 -0
- package/src/react-templates.js +418 -0
- package/src/templates/basic-components-templates.js +157 -0
- package/src/templates/form-components-templates.js +194 -0
- package/src/templates/interactive-components-templates.js +139 -0
- package/src/utils/file-utils.js +97 -0
- package/src/utils/path-utils.js +32 -0
- package/src/utils/string-utils.js +51 -0
- package/src/utils/template-loader.js +91 -0
- package/templates/_shared/PATTERNS.md +246 -0
- package/templates/_shared/README.md +74 -0
- package/templates/_shared/base.css +18 -0
- package/templates/blackjack/index.html +1 -1
- package/templates/breakout/index.html +1 -1
- package/templates/connect-four/index.html +1 -1
- package/templates/dice-game/index.html +1 -1
- package/templates/flappy-bird/index.html +1 -1
- package/templates/pong/index.html +1 -1
- package/templates/skeleton/index.html +4 -4
- package/templates/slot-machine/index.html +1 -1
- package/templates/tetris/index.html +1 -1
- package/templates-react/README.md +126 -0
- package/templates-react/alert/Alert.css +158 -0
- package/templates-react/alert/Alert.example.jsx +106 -0
- package/templates-react/alert/Alert.jsx +61 -0
- package/templates-react/badge/Badge.css +196 -0
- package/templates-react/badge/Badge.example.jsx +182 -0
- package/templates-react/badge/Badge.jsx +44 -0
- package/templates-react/button/Button.css +88 -0
- package/templates-react/button/Button.example.jsx +40 -0
- package/templates-react/button/Button.jsx +29 -0
- package/templates-react/card/Card.css +86 -0
- package/templates-react/card/Card.example.jsx +49 -0
- package/templates-react/card/Card.jsx +35 -0
- package/templates-react/checkbox/Checkbox.css +217 -0
- package/templates-react/checkbox/Checkbox.example.jsx +141 -0
- package/templates-react/checkbox/Checkbox.jsx +82 -0
- package/templates-react/counter/Counter.css +99 -0
- package/templates-react/counter/Counter.example.jsx +45 -0
- package/templates-react/counter/Counter.jsx +70 -0
- package/templates-react/dropdown/Dropdown.css +237 -0
- package/templates-react/dropdown/Dropdown.example.jsx +98 -0
- package/templates-react/dropdown/Dropdown.jsx +154 -0
- package/templates-react/form/Form.css +128 -0
- package/templates-react/form/Form.example.jsx +64 -0
- package/templates-react/form/Form.jsx +125 -0
- package/templates-react/input/Input.css +113 -0
- package/templates-react/input/Input.example.jsx +82 -0
- package/templates-react/input/Input.jsx +87 -0
- package/templates-react/modal/Modal.css +152 -0
- package/templates-react/modal/Modal.example.jsx +90 -0
- package/templates-react/modal/Modal.jsx +46 -0
- package/templates-react/navbar/Navbar.css +139 -0
- package/templates-react/navbar/Navbar.example.jsx +37 -0
- package/templates-react/navbar/Navbar.jsx +62 -0
- package/templates-react/progress/Progress.css +247 -0
- package/templates-react/progress/Progress.example.jsx +244 -0
- package/templates-react/progress/Progress.jsx +79 -0
- package/templates-react/switch/Switch.css +244 -0
- package/templates-react/switch/Switch.example.jsx +221 -0
- package/templates-react/switch/Switch.jsx +98 -0
- package/templates-react/todo-list/TodoList.css +236 -0
- package/templates-react/todo-list/TodoList.example.jsx +15 -0
- package/templates-react/todo-list/TodoList.jsx +84 -0
- package/templates-react/tooltip/Tooltip.css +165 -0
- package/templates-react/tooltip/Tooltip.example.jsx +166 -0
- package/templates-react/tooltip/Tooltip.jsx +176 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import Input from './Input';
|
|
3
|
+
|
|
4
|
+
function InputExample() {
|
|
5
|
+
const [email, setEmail] = useState('');
|
|
6
|
+
const [password, setPassword] = useState('');
|
|
7
|
+
const [emailError, setEmailError] = useState('');
|
|
8
|
+
|
|
9
|
+
const validateEmail = (value) => {
|
|
10
|
+
if (!value) {
|
|
11
|
+
setEmailError('Email is required');
|
|
12
|
+
} else if (!/\S+@\S+\.\S+/.test(value)) {
|
|
13
|
+
setEmailError('Email is invalid');
|
|
14
|
+
} else {
|
|
15
|
+
setEmailError('');
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const handleEmailChange = (e) => {
|
|
20
|
+
setEmail(e.target.value);
|
|
21
|
+
validateEmail(e.target.value);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div style={{ maxWidth: '400px', padding: '2rem' }}>
|
|
26
|
+
<h2>Input Component Examples</h2>
|
|
27
|
+
|
|
28
|
+
{/* Basic Input */}
|
|
29
|
+
<Input
|
|
30
|
+
label="Name"
|
|
31
|
+
placeholder="Enter your name"
|
|
32
|
+
required
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
{/* Email with validation */}
|
|
36
|
+
<Input
|
|
37
|
+
type="email"
|
|
38
|
+
label="Email"
|
|
39
|
+
placeholder="your@email.com"
|
|
40
|
+
value={email}
|
|
41
|
+
onChange={handleEmailChange}
|
|
42
|
+
error={emailError}
|
|
43
|
+
required
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
{/* Password Input */}
|
|
47
|
+
<Input
|
|
48
|
+
type="password"
|
|
49
|
+
label="Password"
|
|
50
|
+
placeholder="Enter password"
|
|
51
|
+
value={password}
|
|
52
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
53
|
+
helperText="Must be at least 8 characters"
|
|
54
|
+
required
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
{/* With Icon */}
|
|
58
|
+
<Input
|
|
59
|
+
label="Search"
|
|
60
|
+
placeholder="Search..."
|
|
61
|
+
icon="🔍"
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
{/* Disabled Input */}
|
|
65
|
+
<Input
|
|
66
|
+
label="Disabled"
|
|
67
|
+
value="This is disabled"
|
|
68
|
+
disabled
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
{/* With max length */}
|
|
72
|
+
<Input
|
|
73
|
+
label="Username"
|
|
74
|
+
placeholder="Max 20 characters"
|
|
75
|
+
maxLength={20}
|
|
76
|
+
helperText="Choose a unique username"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default InputExample;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
function Input({
|
|
5
|
+
type = 'text',
|
|
6
|
+
label,
|
|
7
|
+
placeholder,
|
|
8
|
+
value: controlledValue,
|
|
9
|
+
onChange,
|
|
10
|
+
onBlur,
|
|
11
|
+
error,
|
|
12
|
+
helperText,
|
|
13
|
+
required = false,
|
|
14
|
+
disabled = false,
|
|
15
|
+
maxLength,
|
|
16
|
+
pattern,
|
|
17
|
+
icon,
|
|
18
|
+
className = ''
|
|
19
|
+
}) {
|
|
20
|
+
const [value, setValue] = useState(controlledValue || '');
|
|
21
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
22
|
+
const [touched, setTouched] = useState(false);
|
|
23
|
+
|
|
24
|
+
const isControlled = controlledValue !== undefined;
|
|
25
|
+
const currentValue = isControlled ? controlledValue : value;
|
|
26
|
+
|
|
27
|
+
const handleChange = (e) => {
|
|
28
|
+
const newValue = e.target.value;
|
|
29
|
+
if (!isControlled) {
|
|
30
|
+
setValue(newValue);
|
|
31
|
+
}
|
|
32
|
+
if (onChange) {
|
|
33
|
+
onChange(e);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleFocus = () => {
|
|
38
|
+
setIsFocused(true);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleBlur = (e) => {
|
|
42
|
+
setIsFocused(false);
|
|
43
|
+
setTouched(true);
|
|
44
|
+
if (onBlur) {
|
|
45
|
+
onBlur(e);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const showError = touched && error;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className={`input-wrapper ${className}`}>
|
|
53
|
+
{label && (
|
|
54
|
+
<label className="input-label">
|
|
55
|
+
{label}
|
|
56
|
+
{required && <span className="input-required">*</span>}
|
|
57
|
+
</label>
|
|
58
|
+
)}
|
|
59
|
+
|
|
60
|
+
<div className={`input-container ${isFocused ? 'focused' : ''} ${showError ? 'error' : ''} ${disabled ? 'disabled' : ''}`}>
|
|
61
|
+
{icon && <span className="input-icon">{icon}</span>}
|
|
62
|
+
|
|
63
|
+
<input
|
|
64
|
+
type={type}
|
|
65
|
+
className="input-field"
|
|
66
|
+
placeholder={placeholder}
|
|
67
|
+
value={currentValue}
|
|
68
|
+
onChange={handleChange}
|
|
69
|
+
onFocus={handleFocus}
|
|
70
|
+
onBlur={handleBlur}
|
|
71
|
+
disabled={disabled}
|
|
72
|
+
required={required}
|
|
73
|
+
maxLength={maxLength}
|
|
74
|
+
pattern={pattern}
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
{(showError || helperText) && (
|
|
79
|
+
<div className={`input-message ${showError ? 'error' : ''}`}>
|
|
80
|
+
{showError ? error : helperText}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default Input;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/* Modal Styles */
|
|
2
|
+
.modal-overlay {
|
|
3
|
+
position: fixed;
|
|
4
|
+
top: 0;
|
|
5
|
+
left: 0;
|
|
6
|
+
right: 0;
|
|
7
|
+
bottom: 0;
|
|
8
|
+
background: rgba(0, 0, 0, 0.6);
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
z-index: 1000;
|
|
13
|
+
animation: fadeIn 0.3s ease;
|
|
14
|
+
backdrop-filter: blur(4px);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@keyframes fadeIn {
|
|
18
|
+
from {
|
|
19
|
+
opacity: 0;
|
|
20
|
+
}
|
|
21
|
+
to {
|
|
22
|
+
opacity: 1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.modal-content {
|
|
27
|
+
background: white;
|
|
28
|
+
border-radius: 16px;
|
|
29
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
30
|
+
max-height: 90vh;
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
animation: slideUp 0.3s ease;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@keyframes slideUp {
|
|
38
|
+
from {
|
|
39
|
+
transform: translateY(30px);
|
|
40
|
+
opacity: 0;
|
|
41
|
+
}
|
|
42
|
+
to {
|
|
43
|
+
transform: translateY(0);
|
|
44
|
+
opacity: 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.modal-small {
|
|
49
|
+
width: 90%;
|
|
50
|
+
max-width: 400px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.modal-medium {
|
|
54
|
+
width: 90%;
|
|
55
|
+
max-width: 600px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.modal-large {
|
|
59
|
+
width: 90%;
|
|
60
|
+
max-width: 900px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.modal-header {
|
|
64
|
+
padding: 24px 32px;
|
|
65
|
+
border-bottom: 1px solid #e2e8f0;
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
justify-content: space-between;
|
|
69
|
+
background: linear-gradient(135deg, {{primaryColor}}15 0%, {{secondaryColor}}15 100%);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.modal-title {
|
|
73
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
74
|
+
font-size: 24px;
|
|
75
|
+
font-weight: 700;
|
|
76
|
+
margin: 0;
|
|
77
|
+
background: linear-gradient(135deg, {{primaryColor}} 0%, {{secondaryColor}} 100%);
|
|
78
|
+
-webkit-background-clip: text;
|
|
79
|
+
-webkit-text-fill-color: transparent;
|
|
80
|
+
background-clip: text;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.modal-close {
|
|
84
|
+
font-size: 36px;
|
|
85
|
+
font-weight: 300;
|
|
86
|
+
background: none;
|
|
87
|
+
border: none;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
color: #718096;
|
|
90
|
+
padding: 0;
|
|
91
|
+
width: 36px;
|
|
92
|
+
height: 36px;
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
border-radius: 50%;
|
|
97
|
+
transition: all 0.2s ease;
|
|
98
|
+
line-height: 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.modal-close:hover {
|
|
102
|
+
background: #f7fafc;
|
|
103
|
+
color: #2d3748;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.modal-body {
|
|
107
|
+
padding: 32px;
|
|
108
|
+
overflow-y: auto;
|
|
109
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
110
|
+
font-size: 16px;
|
|
111
|
+
line-height: 1.6;
|
|
112
|
+
color: #2d3748;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.modal-footer {
|
|
116
|
+
padding: 20px 32px;
|
|
117
|
+
border-top: 1px solid #e2e8f0;
|
|
118
|
+
display: flex;
|
|
119
|
+
gap: 12px;
|
|
120
|
+
justify-content: flex-end;
|
|
121
|
+
background: #f7fafc;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Dark Mode Support */
|
|
125
|
+
@media (prefers-color-scheme: dark) {
|
|
126
|
+
.modal-content {
|
|
127
|
+
background: #2d3748;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.modal-header {
|
|
131
|
+
border-bottom-color: #4a5568;
|
|
132
|
+
background: linear-gradient(135deg, {{primaryColor}}20 0%, {{secondaryColor}}20 100%);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.modal-body {
|
|
136
|
+
color: #e2e8f0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.modal-footer {
|
|
140
|
+
background: #1a202c;
|
|
141
|
+
border-top-color: #4a5568;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.modal-close {
|
|
145
|
+
color: #cbd5e0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.modal-close:hover {
|
|
149
|
+
background: #4a5568;
|
|
150
|
+
color: #e2e8f0;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import Modal from './Modal';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Example usage of Modal component
|
|
6
|
+
*/
|
|
7
|
+
const ModalExample = () => {
|
|
8
|
+
const [isSmallOpen, setIsSmallOpen] = useState(false);
|
|
9
|
+
const [isMediumOpen, setIsMediumOpen] = useState(false);
|
|
10
|
+
const [isLargeOpen, setIsLargeOpen] = useState(false);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div style={{ padding: '40px' }}>
|
|
14
|
+
<h2 style={{ marginBottom: '30px' }}>Modal Component Examples</h2>
|
|
15
|
+
|
|
16
|
+
<div style={{ display: 'flex', gap: '15px', flexWrap: 'wrap' }}>
|
|
17
|
+
<button
|
|
18
|
+
onClick={() => setIsSmallOpen(true)}
|
|
19
|
+
style={{ padding: '12px 24px', background: '#3182ce', color: 'white', border: 'none', borderRadius: '8px', cursor: 'pointer' }}
|
|
20
|
+
>
|
|
21
|
+
Open Small Modal
|
|
22
|
+
</button>
|
|
23
|
+
|
|
24
|
+
<button
|
|
25
|
+
onClick={() => setIsMediumOpen(true)}
|
|
26
|
+
style={{ padding: '12px 24px', background: '#3182ce', color: 'white', border: 'none', borderRadius: '8px', cursor: 'pointer' }}
|
|
27
|
+
>
|
|
28
|
+
Open Medium Modal
|
|
29
|
+
</button>
|
|
30
|
+
|
|
31
|
+
<button
|
|
32
|
+
onClick={() => setIsLargeOpen(true)}
|
|
33
|
+
style={{ padding: '12px 24px', background: '#3182ce', color: 'white', border: 'none', borderRadius: '8px', cursor: 'pointer' }}
|
|
34
|
+
>
|
|
35
|
+
Open Large Modal
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<Modal
|
|
40
|
+
isOpen={isSmallOpen}
|
|
41
|
+
onClose={() => setIsSmallOpen(false)}
|
|
42
|
+
title="Small Modal"
|
|
43
|
+
size="small"
|
|
44
|
+
footer={
|
|
45
|
+
<>
|
|
46
|
+
<button
|
|
47
|
+
onClick={() => setIsSmallOpen(false)}
|
|
48
|
+
style={{ padding: '10px 20px', background: '#e2e8f0', border: 'none', borderRadius: '6px', cursor: 'pointer' }}
|
|
49
|
+
>
|
|
50
|
+
Cancel
|
|
51
|
+
</button>
|
|
52
|
+
<button
|
|
53
|
+
onClick={() => { console.log('Confirmed!'); setIsSmallOpen(false); }}
|
|
54
|
+
style={{ padding: '10px 20px', background: '#3182ce', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer' }}
|
|
55
|
+
>
|
|
56
|
+
Confirm
|
|
57
|
+
</button>
|
|
58
|
+
</>
|
|
59
|
+
}
|
|
60
|
+
>
|
|
61
|
+
<p>This is a small modal perfect for confirmation dialogs or short messages.</p>
|
|
62
|
+
</Modal>
|
|
63
|
+
|
|
64
|
+
<Modal
|
|
65
|
+
isOpen={isMediumOpen}
|
|
66
|
+
onClose={() => setIsMediumOpen(false)}
|
|
67
|
+
title="Medium Modal"
|
|
68
|
+
size="medium"
|
|
69
|
+
>
|
|
70
|
+
<h3>Modal Content</h3>
|
|
71
|
+
<p>This is a medium-sized modal suitable for forms or detailed information.</p>
|
|
72
|
+
<p>You can include any React components or HTML elements inside the modal.</p>
|
|
73
|
+
</Modal>
|
|
74
|
+
|
|
75
|
+
<Modal
|
|
76
|
+
isOpen={isLargeOpen}
|
|
77
|
+
onClose={() => setIsLargeOpen(false)}
|
|
78
|
+
title="Large Modal"
|
|
79
|
+
size="large"
|
|
80
|
+
>
|
|
81
|
+
<h3>Large Modal Content</h3>
|
|
82
|
+
<p>This is a large modal ideal for complex interfaces or detailed content.</p>
|
|
83
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
|
84
|
+
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
|
85
|
+
</Modal>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default ModalExample;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import './Modal.css';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Modal Component
|
|
6
|
+
* A flexible modal dialog component
|
|
7
|
+
*/
|
|
8
|
+
const Modal = ({
|
|
9
|
+
isOpen,
|
|
10
|
+
onClose,
|
|
11
|
+
title,
|
|
12
|
+
children,
|
|
13
|
+
footer,
|
|
14
|
+
showCloseButton = true,
|
|
15
|
+
closeOnOverlayClick = true,
|
|
16
|
+
size = 'medium'
|
|
17
|
+
}) => {
|
|
18
|
+
if (!isOpen) return null;
|
|
19
|
+
|
|
20
|
+
const handleOverlayClick = (e) => {
|
|
21
|
+
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
22
|
+
onClose?.();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="modal-overlay" onClick={handleOverlayClick}>
|
|
28
|
+
<div className={`modal-content modal-${size}`}>
|
|
29
|
+
<div className="modal-header">
|
|
30
|
+
{title && <h2 className="modal-title">{title}</h2>}
|
|
31
|
+
{showCloseButton && (
|
|
32
|
+
<button className="modal-close" onClick={onClose} aria-label="Close">
|
|
33
|
+
×
|
|
34
|
+
</button>
|
|
35
|
+
)}
|
|
36
|
+
</div>
|
|
37
|
+
<div className="modal-body">
|
|
38
|
+
{children}
|
|
39
|
+
</div>
|
|
40
|
+
{footer && <div className="modal-footer">{footer}</div>}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default Modal;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
.navbar {
|
|
2
|
+
background: linear-gradient(135deg, ##PRIMARY_COLOR## 0%, ##SECONDARY_COLOR## 100%);
|
|
3
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
4
|
+
position: sticky;
|
|
5
|
+
top: 0;
|
|
6
|
+
width: 100%;
|
|
7
|
+
z-index: 1000;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.navbar-container {
|
|
11
|
+
max-width: 1200px;
|
|
12
|
+
margin: 0 auto;
|
|
13
|
+
padding: 1rem 2rem;
|
|
14
|
+
display: flex;
|
|
15
|
+
justify-content: space-between;
|
|
16
|
+
align-items: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.navbar-logo {
|
|
20
|
+
font-size: 1.5rem;
|
|
21
|
+
font-weight: bold;
|
|
22
|
+
color: white;
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
user-select: none;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.navbar-toggle {
|
|
28
|
+
display: none;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
background: none;
|
|
31
|
+
border: none;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
padding: 0.5rem;
|
|
34
|
+
z-index: 1001;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.navbar-toggle span {
|
|
38
|
+
width: 25px;
|
|
39
|
+
height: 3px;
|
|
40
|
+
background: white;
|
|
41
|
+
margin: 3px 0;
|
|
42
|
+
transition: all 0.3s ease;
|
|
43
|
+
border-radius: 3px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.navbar-toggle.active span:nth-child(1) {
|
|
47
|
+
transform: rotate(45deg) translate(8px, 8px);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.navbar-toggle.active span:nth-child(2) {
|
|
51
|
+
opacity: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.navbar-toggle.active span:nth-child(3) {
|
|
55
|
+
transform: rotate(-45deg) translate(7px, -7px);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.navbar-menu {
|
|
59
|
+
display: flex;
|
|
60
|
+
list-style: none;
|
|
61
|
+
margin: 0;
|
|
62
|
+
padding: 0;
|
|
63
|
+
gap: 2rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.navbar-item {
|
|
67
|
+
position: relative;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.navbar-link {
|
|
71
|
+
color: white;
|
|
72
|
+
text-decoration: none;
|
|
73
|
+
font-size: 1rem;
|
|
74
|
+
font-weight: 500;
|
|
75
|
+
padding: 0.5rem 0;
|
|
76
|
+
transition: opacity 0.3s ease;
|
|
77
|
+
position: relative;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.navbar-link::after {
|
|
81
|
+
content: '';
|
|
82
|
+
position: absolute;
|
|
83
|
+
bottom: 0;
|
|
84
|
+
left: 0;
|
|
85
|
+
width: 0;
|
|
86
|
+
height: 2px;
|
|
87
|
+
background: white;
|
|
88
|
+
transition: width 0.3s ease;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.navbar-link:hover {
|
|
92
|
+
opacity: 0.8;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.navbar-link:hover::after {
|
|
96
|
+
width: 100%;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* Mobile Responsive */
|
|
100
|
+
@media (max-width: 768px) {
|
|
101
|
+
.navbar-toggle {
|
|
102
|
+
display: flex;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.navbar-menu {
|
|
106
|
+
position: fixed;
|
|
107
|
+
top: 0;
|
|
108
|
+
right: -100%;
|
|
109
|
+
height: 100vh;
|
|
110
|
+
width: 70%;
|
|
111
|
+
max-width: 300px;
|
|
112
|
+
background: linear-gradient(135deg, ##PRIMARY_COLOR## 0%, ##SECONDARY_COLOR## 100%);
|
|
113
|
+
flex-direction: column;
|
|
114
|
+
padding: 5rem 2rem 2rem;
|
|
115
|
+
gap: 1.5rem;
|
|
116
|
+
transition: right 0.3s ease;
|
|
117
|
+
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.2);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.navbar-menu.active {
|
|
121
|
+
right: 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.navbar-link {
|
|
125
|
+
font-size: 1.1rem;
|
|
126
|
+
padding: 0.5rem 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.navbar-link::after {
|
|
130
|
+
display: none;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Dark mode support */
|
|
135
|
+
@media (prefers-color-scheme: dark) {
|
|
136
|
+
.navbar {
|
|
137
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Navbar from './Navbar';
|
|
2
|
+
|
|
3
|
+
function NavbarExample() {
|
|
4
|
+
const customLinks = [
|
|
5
|
+
{ label: 'Home', href: '#home' },
|
|
6
|
+
{ label: 'Features', href: '#features' },
|
|
7
|
+
{ label: 'Pricing', href: '#pricing' },
|
|
8
|
+
{ label: 'About', href: '#about' },
|
|
9
|
+
{ label: 'Contact', href: '#contact' }
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const handleLinkClick = (link) => {
|
|
13
|
+
console.log('Clicked:', link.label);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div>
|
|
18
|
+
{/* Basic usage */}
|
|
19
|
+
<Navbar />
|
|
20
|
+
|
|
21
|
+
{/* With custom logo and links */}
|
|
22
|
+
<Navbar
|
|
23
|
+
logo="MyApp"
|
|
24
|
+
links={customLinks}
|
|
25
|
+
onLinkClick={handleLinkClick}
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
{/* With logo as component */}
|
|
29
|
+
<Navbar
|
|
30
|
+
logo={<img src="/logo.png" alt="Logo" style={{ height: '30px' }} />}
|
|
31
|
+
links={customLinks}
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default NavbarExample;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
function Navbar({
|
|
5
|
+
logo = 'Logo',
|
|
6
|
+
links = [
|
|
7
|
+
{ label: 'Home', href: '#home' },
|
|
8
|
+
{ label: 'About', href: '#about' },
|
|
9
|
+
{ label: 'Services', href: '#services' },
|
|
10
|
+
{ label: 'Contact', href: '#contact' }
|
|
11
|
+
],
|
|
12
|
+
onLinkClick
|
|
13
|
+
}) {
|
|
14
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
15
|
+
|
|
16
|
+
const toggleMenu = () => {
|
|
17
|
+
setIsOpen(!isOpen);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const handleLinkClick = (link) => {
|
|
21
|
+
setIsOpen(false);
|
|
22
|
+
if (onLinkClick) {
|
|
23
|
+
onLinkClick(link);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<nav className="navbar">
|
|
29
|
+
<div className="navbar-container">
|
|
30
|
+
<div className="navbar-logo">
|
|
31
|
+
{logo}
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<button
|
|
35
|
+
className={`navbar-toggle ${isOpen ? 'active' : ''}`}
|
|
36
|
+
onClick={toggleMenu}
|
|
37
|
+
aria-label="Toggle menu"
|
|
38
|
+
>
|
|
39
|
+
<span></span>
|
|
40
|
+
<span></span>
|
|
41
|
+
<span></span>
|
|
42
|
+
</button>
|
|
43
|
+
|
|
44
|
+
<ul className={`navbar-menu ${isOpen ? 'active' : ''}`}>
|
|
45
|
+
{links.map((link, index) => (
|
|
46
|
+
<li key={index} className="navbar-item">
|
|
47
|
+
<a
|
|
48
|
+
href={link.href}
|
|
49
|
+
className="navbar-link"
|
|
50
|
+
onClick={() => handleLinkClick(link)}
|
|
51
|
+
>
|
|
52
|
+
{link.label}
|
|
53
|
+
</a>
|
|
54
|
+
</li>
|
|
55
|
+
))}
|
|
56
|
+
</ul>
|
|
57
|
+
</div>
|
|
58
|
+
</nav>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default Navbar;
|