tee3apps-cms-sdk-react 0.0.6 → 0.0.7

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.
@@ -65,3 +65,149 @@
65
65
  opacity: 0.4;
66
66
  }
67
67
  }
68
+
69
+ /* Toast Notification Styles */
70
+ .toast-container {
71
+ position: fixed;
72
+ top: 20px;
73
+ right: 20px;
74
+ z-index: 10000;
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 12px;
78
+ max-width: 400px;
79
+ width: calc(100% - 40px);
80
+ pointer-events: none;
81
+ }
82
+
83
+ @media (max-width: 480px) {
84
+ .toast-container {
85
+ top: 10px;
86
+ right: 10px;
87
+ left: 10px;
88
+ width: calc(100% - 20px);
89
+ max-width: none;
90
+ }
91
+ }
92
+
93
+ .toast {
94
+ display: flex;
95
+ align-items: flex-start;
96
+ gap: 12px;
97
+ padding: 16px;
98
+ background-color: #ffffff;
99
+ border-radius: 8px;
100
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
101
+ border-left: 4px solid;
102
+ cursor: pointer;
103
+ pointer-events: all;
104
+ animation: slideInRight 0.3s ease-out, fadeOut 0.3s ease-out 4.7s forwards;
105
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
106
+ }
107
+
108
+ .toast:hover {
109
+ transform: translateX(-4px);
110
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
111
+ }
112
+
113
+ .toast--error {
114
+ border-left-color: #ef4444;
115
+ background-color: #fef2f2;
116
+ }
117
+
118
+ .toast--error .toast__icon {
119
+ color: #ef4444;
120
+ }
121
+
122
+ .toast--success {
123
+ border-left-color: #10b981;
124
+ background-color: #f0fdf4;
125
+ }
126
+
127
+ .toast--success .toast__icon {
128
+ color: #10b981;
129
+ }
130
+
131
+ .toast--info {
132
+ border-left-color: #3b82f6;
133
+ background-color: #eff6ff;
134
+ }
135
+
136
+ .toast--info .toast__icon {
137
+ color: #3b82f6;
138
+ }
139
+
140
+ .toast--warning {
141
+ border-left-color: #f59e0b;
142
+ background-color: #fffbeb;
143
+ }
144
+
145
+ .toast--warning .toast__icon {
146
+ color: #f59e0b;
147
+ }
148
+
149
+ .toast__icon {
150
+ flex-shrink: 0;
151
+ display: flex;
152
+ align-items: center;
153
+ justify-content: center;
154
+ width: 20px;
155
+ height: 20px;
156
+ }
157
+
158
+ .toast__message {
159
+ flex: 1;
160
+ font-size: 14px;
161
+ line-height: 1.5;
162
+ color: #1f2937;
163
+ font-weight: 500;
164
+ word-wrap: break-word;
165
+ overflow-wrap: break-word;
166
+ }
167
+
168
+ .toast__close {
169
+ flex-shrink: 0;
170
+ background: none;
171
+ border: none;
172
+ padding: 4px;
173
+ cursor: pointer;
174
+ color: #6b7280;
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: center;
178
+ border-radius: 4px;
179
+ transition: background-color 0.2s ease, color 0.2s ease;
180
+ margin-left: 4px;
181
+ }
182
+
183
+ .toast__close:hover {
184
+ background-color: rgba(0, 0, 0, 0.05);
185
+ color: #1f2937;
186
+ }
187
+
188
+ .toast__close:active {
189
+ background-color: rgba(0, 0, 0, 0.1);
190
+ }
191
+
192
+ /* Animations */
193
+ @keyframes slideInRight {
194
+ from {
195
+ transform: translateX(100%);
196
+ opacity: 0;
197
+ }
198
+ to {
199
+ transform: translateX(0);
200
+ opacity: 1;
201
+ }
202
+ }
203
+
204
+ @keyframes fadeOut {
205
+ from {
206
+ opacity: 1;
207
+ transform: translateX(0);
208
+ }
209
+ to {
210
+ opacity: 0;
211
+ transform: translateX(100%);
212
+ }
213
+ }
@@ -8,11 +8,18 @@ interface PageFormProps {
8
8
  onSubmit?: (data: Record<string, any>) => void;
9
9
  }
10
10
 
11
+ interface Toast {
12
+ id: string;
13
+ message: string;
14
+ type: 'error' | 'success' | 'info' | 'warning';
15
+ }
16
+
11
17
  const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
12
18
  // Form state to store all field values
13
19
  const [formValues, setFormValues] = useState<Record<string, any>>({});
14
20
  const [deviceMode, setDeviceMode] = useState<string>('web');
15
21
  const [validationErrors, setValidationErrors] = useState<Record<string, boolean>>({});
22
+ const [toasts, setToasts] = useState<Toast[]>([]);
16
23
 
17
24
  // Function to determine device mode based on screen width
18
25
  const getDeviceMode = (width: number) => {
@@ -146,6 +153,24 @@ const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
146
153
  return { isValid: Object.keys(errors).length === 0, errors };
147
154
  }, [formValues, getRequiredFields]);
148
155
 
156
+ // Function to show toast notification
157
+ const showToast = useCallback((message: string, type: Toast['type'] = 'error') => {
158
+ const id = Date.now().toString();
159
+ const newToast: Toast = { id, message, type };
160
+
161
+ setToasts(prev => [...prev, newToast]);
162
+
163
+ // Auto remove toast after 5 seconds
164
+ setTimeout(() => {
165
+ setToasts(prev => prev.filter(toast => toast.id !== id));
166
+ }, 5000);
167
+ }, []);
168
+
169
+ // Function to remove toast
170
+ const removeToast = useCallback((id: string) => {
171
+ setToasts(prev => prev.filter(toast => toast.id !== id));
172
+ }, []);
173
+
149
174
  // Single method to handle form submission - validates, transforms, and sends data to parent
150
175
  const handleFormSubmit = useCallback(() => {
151
176
  // Validate form and get validation result
@@ -165,9 +190,9 @@ const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
165
190
  const errorFields = Object.keys(errors)
166
191
  .map(code => requiredFields[code]?.name || code)
167
192
  .join(', ');
168
- alert(`Please fill in all required fields: ${errorFields}`);
193
+ showToast(`Please fill in all required fields: ${errorFields}`, 'error');
169
194
  }
170
- }, [formValues, validateForm, getRequiredFields, transformFormValuesToNames, onSubmit]);
195
+ }, [formValues, validateForm, getRequiredFields, transformFormValuesToNames, onSubmit, showToast]);
171
196
 
172
197
  return (
173
198
  <div className="page-form">
@@ -188,6 +213,53 @@ const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
188
213
  {/* Footer */}
189
214
 
190
215
  </div>
216
+
217
+ {/* Toast Container */}
218
+ <div className="toast-container">
219
+ {toasts.map((toast) => (
220
+ <div
221
+ key={toast.id}
222
+ className={`toast toast--${toast.type}`}
223
+ onClick={() => removeToast(toast.id)}
224
+ >
225
+ <div className="toast__icon">
226
+ {toast.type === 'error' && (
227
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
228
+ <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
229
+ </svg>
230
+ )}
231
+ {toast.type === 'success' && (
232
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
233
+ <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
234
+ </svg>
235
+ )}
236
+ {toast.type === 'info' && (
237
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
238
+ <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
239
+ </svg>
240
+ )}
241
+ {toast.type === 'warning' && (
242
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
243
+ <path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
244
+ </svg>
245
+ )}
246
+ </div>
247
+ <div className="toast__message">{toast.message}</div>
248
+ <button
249
+ className="toast__close"
250
+ onClick={(e) => {
251
+ e.stopPropagation();
252
+ removeToast(toast.id);
253
+ }}
254
+ aria-label="Close"
255
+ >
256
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
257
+ <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
258
+ </svg>
259
+ </button>
260
+ </div>
261
+ ))}
262
+ </div>
191
263
  </div>
192
264
  );
193
265
  };
@@ -3,28 +3,28 @@ import type { ComponentProps } from '../types';
3
3
 
4
4
  const TextComponent: React.FC<{ props: ComponentProps }> = ({ props }) => {
5
5
  const { text, style } = props;
6
- const Tag = (style?.headingTag as keyof JSX.IntrinsicElements) || 'p';
7
-
6
+
8
7
  if (!text?.all) {
9
- return <div className="text-sm text-gray-500">No text content</div>;
8
+ return <span className="text-sm text-gray-500">No text content</span>;
10
9
  }
11
10
 
11
+ const textStyle: React.CSSProperties = {
12
+ fontSize: style?.fontSize ? `${style.fontSize}px` : '16px',
13
+ fontWeight: style?.fontStyle?.isBold ? 'bold' : 'normal',
14
+ fontStyle: style?.fontStyle?.isItalic ? 'italic' : 'normal',
15
+ textDecoration: style?.fontStyle?.isUnderLine ? 'underline' : 'none',
16
+ textDecorationLine: style?.fontStyle?.isStrikeThrough ? 'line-through' : 'none',
17
+ color: style?.fontColor || '#000',
18
+ textAlign: (style?.textAlign as any) || 'left',
19
+ fontFamily: style?.fontFamily || 'inherit',
20
+ margin: 5,
21
+ display: 'inline', // keeps it inline instead of block like p/div
22
+ };
23
+
12
24
  const textElement = (
13
- <Tag
14
- style={{
15
- fontSize: style?.fontSize ? `${style.fontSize}px` : '16px',
16
- fontWeight: style?.fontStyle?.isBold ? 'bold' : 'normal',
17
- fontStyle: style?.fontStyle?.isItalic ? 'italic' : 'normal',
18
- textDecoration: style?.fontStyle?.isUnderLine ? 'underline' : 'none',
19
- textDecorationLine: style?.fontStyle?.isStrikeThrough ? 'line-through' : 'none',
20
- color: style?.fontColor || '#000',
21
- textAlign: (style?.textAlign as any) || 'left',
22
- fontFamily: style?.fontFamily || 'inherit',
23
- margin: 5,
24
- }}
25
- >
25
+ <span style={textStyle}>
26
26
  {text.all}
27
- </Tag>
27
+ </span>
28
28
  );
29
29
 
30
30
  if (props.linktype === 'EXTERNAL' && props.link?.url) {
@@ -42,4 +42,4 @@ const TextComponent: React.FC<{ props: ComponentProps }> = ({ props }) => {
42
42
  return textElement;
43
43
  };
44
44
 
45
- export default TextComponent;
45
+ export default TextComponent;