@qwickapps/react-framework 1.4.8 → 1.4.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.
Files changed (42) hide show
  1. package/README.md +13 -3
  2. package/dist/index.css +1 -1
  3. package/dist/index.esm.css +1 -1
  4. package/dist/index.esm.js +255 -54
  5. package/dist/index.js +249 -48
  6. package/dist/src/components/Logo.d.ts.map +1 -1
  7. package/dist/src/components/ResponsiveMenu.d.ts.map +1 -1
  8. package/dist/src/components/SafeSpan.d.ts.map +1 -1
  9. package/dist/src/components/Scaffold.d.ts.map +1 -1
  10. package/dist/src/components/blocks/Article.d.ts.map +1 -1
  11. package/dist/src/components/blocks/Footer.d.ts.map +1 -1
  12. package/dist/src/components/blocks/Text.d.ts.map +1 -1
  13. package/dist/src/components/buttons/Button.d.ts +26 -10
  14. package/dist/src/components/buttons/Button.d.ts.map +1 -1
  15. package/dist/src/components/menu/MenuItem.d.ts +2 -2
  16. package/dist/src/components/menu/MenuItem.d.ts.map +1 -1
  17. package/dist/src/schemas/ButtonSchema.d.ts +3 -0
  18. package/dist/src/schemas/ButtonSchema.d.ts.map +1 -1
  19. package/dist/src/schemas/transformers/ComponentTransformer.d.ts.map +1 -1
  20. package/dist/src/schemas/transformers/ReactNodeTransformer.d.ts.map +1 -1
  21. package/package.json +9 -1
  22. package/src/components/Html.tsx +1 -1
  23. package/src/components/Logo.tsx +2 -4
  24. package/src/components/QwickApp.css +19 -13
  25. package/src/components/ResponsiveMenu.tsx +12 -1
  26. package/src/components/SafeSpan.tsx +0 -1
  27. package/src/components/Scaffold.css +14 -0
  28. package/src/components/Scaffold.tsx +16 -2
  29. package/src/components/blocks/Article.tsx +78 -7
  30. package/src/components/blocks/CoverImageHeader.tsx +1 -1
  31. package/src/components/blocks/Footer.tsx +23 -23
  32. package/src/components/blocks/PageBannerHeader.tsx +1 -1
  33. package/src/components/blocks/Text.tsx +7 -4
  34. package/src/components/buttons/Button.tsx +189 -15
  35. package/src/components/menu/MenuItem.tsx +2 -2
  36. package/src/contexts/ThemeContext.tsx +1 -1
  37. package/src/schemas/ButtonSchema.ts +33 -0
  38. package/src/schemas/ViewSchema.ts +1 -1
  39. package/src/schemas/transformers/ComponentTransformer.ts +2 -8
  40. package/src/schemas/transformers/ReactNodeTransformer.ts +1 -2
  41. package/src/stories/Article.stories.tsx +1 -1
  42. package/src/stories/ChoiceInputField.stories.tsx +2 -2
@@ -16,9 +16,106 @@
16
16
 
17
17
  import React, { ReactElement } from 'react';
18
18
  import { Button as MuiButton, ButtonProps as MuiButtonProps, CircularProgress } from '@mui/material';
19
+ import {
20
+ Home,
21
+ Download,
22
+ Settings,
23
+ Dashboard,
24
+ Info,
25
+ Help,
26
+ Add,
27
+ Edit,
28
+ Delete,
29
+ Check,
30
+ Close,
31
+ ArrowForward,
32
+ ArrowBack,
33
+ Menu,
34
+ Search,
35
+ Favorite,
36
+ Star,
37
+ Share,
38
+ CloudUpload,
39
+ CloudDownload,
40
+ Save,
41
+ Send,
42
+ Email,
43
+ Phone,
44
+ Person,
45
+ Group,
46
+ Business,
47
+ ShoppingCart,
48
+ AttachMoney,
49
+ Lock,
50
+ LockOpen,
51
+ Visibility,
52
+ VisibilityOff,
53
+ } from '@mui/icons-material';
19
54
  import ButtonModel from '../../schemas/ButtonSchema';
20
55
  import { createSerializableView, SerializableComponent } from '../shared/createSerializableView';
21
56
  import { ViewProps } from '../shared/viewProps';
57
+ import type { SchemaProps } from '@qwickapps/schema/src/types/ModelProps';
58
+
59
+ /**
60
+ * Icon name to Material-UI icon component mapping
61
+ * Used by finalize function to transform icon string names to React components
62
+ */
63
+ const iconRegistry: Record<string, React.ComponentType> = {
64
+ home: Home,
65
+ download: Download,
66
+ clouddownload: CloudDownload,
67
+ cloudupload: CloudUpload,
68
+ settings: Settings,
69
+ dashboard: Dashboard,
70
+ info: Info,
71
+ help: Help,
72
+ add: Add,
73
+ edit: Edit,
74
+ delete: Delete,
75
+ check: Check,
76
+ close: Close,
77
+ arrowforward: ArrowForward,
78
+ arrowback: ArrowBack,
79
+ menu: Menu,
80
+ search: Search,
81
+ favorite: Favorite,
82
+ star: Star,
83
+ share: Share,
84
+ save: Save,
85
+ send: Send,
86
+ email: Email,
87
+ phone: Phone,
88
+ person: Person,
89
+ group: Group,
90
+ business: Business,
91
+ shoppingcart: ShoppingCart,
92
+ attachmoney: AttachMoney,
93
+ lock: Lock,
94
+ lockopen: LockOpen,
95
+ visibility: Visibility,
96
+ visibilityoff: VisibilityOff,
97
+ };
98
+
99
+ /**
100
+ * Get icon component from icon name string
101
+ * Exported for use by other components (Scaffold, ResponsiveMenu, etc.)
102
+ */
103
+ export function getIconComponent(iconName: string | undefined): React.ReactElement | null {
104
+ if (!iconName) return null;
105
+
106
+ const IconComponent = iconRegistry[iconName.toLowerCase()];
107
+ if (!IconComponent) {
108
+ console.warn(`[Button] Icon "${iconName}" not found in registry`);
109
+ return null;
110
+ }
111
+
112
+ return <IconComponent />;
113
+ }
114
+
115
+ /**
116
+ * Export icon registry for extension by applications
117
+ */
118
+ export { iconRegistry };
22
119
 
23
120
  // Action serialization pattern for button clicks
24
121
  export interface ButtonAction {
@@ -29,27 +126,28 @@ export interface ButtonAction {
29
126
  customHandler?: string;
30
127
  }
31
128
 
32
- export interface ButtonProps extends ViewProps {
33
- // Component-specific props from ButtonModel
34
- label?: string;
35
- variant?: 'primary' | 'secondary' | 'outlined' | 'text' | 'contained';
36
- buttonSize?: 'small' | 'medium' | 'large';
37
- href?: string;
38
- target?: '_blank' | '_self' | '_parent' | '_top';
39
- disabled?: boolean;
40
- loading?: boolean;
41
- fullWidth?: boolean;
42
- // Additional props for enhanced functionality
129
+ /**
130
+ * Props interface for Button component
131
+ * Uses SchemaProps<typeof ButtonSchema> for schema-driven typing
132
+ * Icons are transformed from strings to React components by finalize function
133
+ *
134
+ * Note: We omit 'icon' and 'endIcon' from schema props because they are strings in the schema
135
+ * but get transformed to ReactElements by the finalize function.
136
+ */
137
+ export type ButtonProps = ViewProps & Omit<SchemaProps<typeof ButtonModel>, 'icon' | 'endIcon'> & {
138
+ // Runtime props (transformed from schema strings by finalize function)
43
139
  icon?: React.ReactNode;
44
140
  endIcon?: React.ReactNode;
141
+ // Additional runtime-only props
45
142
  action?: ButtonAction;
46
- }
143
+ };
47
144
 
48
145
  // View component - handles the actual rendering
49
146
  const ButtonView = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
50
147
  const {
51
148
  label,
52
149
  variant = 'primary',
150
+ color,
53
151
  buttonSize = 'medium',
54
152
  icon,
55
153
  endIcon,
@@ -82,8 +180,14 @@ const ButtonView = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref)
82
180
  }
83
181
  };
84
182
 
85
- // Map our variants to MUI colors
86
- const getMuiColor = (): MuiButtonProps['color'] => {
183
+ // Get theme color name - prioritize explicit color prop over variant-derived color
184
+ const getThemeColorName = (): string => {
185
+ // If color prop is explicitly provided, use it
186
+ if (color) {
187
+ return color;
188
+ }
189
+
190
+ // Otherwise derive from variant
87
191
  switch (variant) {
88
192
  case 'primary':
89
193
  return 'primary';
@@ -100,6 +204,54 @@ const ButtonView = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref)
100
204
  }
101
205
  };
102
206
 
207
+ // Get CSS theme variable styles based on variant and color
208
+ const getColorStyles = () => {
209
+ const muiVariant = getMuiVariant();
210
+ const colorName = getThemeColorName();
211
+
212
+ // For contained buttons: use solid background
213
+ if (muiVariant === 'contained') {
214
+ return {
215
+ backgroundColor: `var(--theme-${colorName})`,
216
+ color: `var(--theme-on-${colorName})`,
217
+ '&:hover': {
218
+ backgroundColor: `var(--theme-${colorName}-dark)`,
219
+ },
220
+ '&.Mui-disabled': {
221
+ backgroundColor: 'var(--theme-text-disabled)',
222
+ color: 'var(--theme-on-surface)',
223
+ }
224
+ };
225
+ }
226
+
227
+ // For outlined buttons: border and text color
228
+ if (muiVariant === 'outlined') {
229
+ return {
230
+ borderColor: `var(--theme-${colorName})`,
231
+ color: `var(--theme-${colorName})`,
232
+ '&:hover': {
233
+ borderColor: `var(--theme-${colorName}-dark)`,
234
+ backgroundColor: `var(--theme-${colorName}-light)`,
235
+ },
236
+ '&.Mui-disabled': {
237
+ borderColor: 'var(--theme-border-main)',
238
+ color: 'var(--theme-text-disabled)',
239
+ }
240
+ };
241
+ }
242
+
243
+ // For text buttons: just text color
244
+ return {
245
+ color: `var(--theme-${colorName})`,
246
+ '&:hover': {
247
+ backgroundColor: `var(--theme-${colorName}-light)`,
248
+ },
249
+ '&.Mui-disabled': {
250
+ color: 'var(--theme-text-disabled)',
251
+ }
252
+ };
253
+ };
254
+
103
255
  // Handle action serialization pattern for onClick
104
256
  const handleActionClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
105
257
  if (action) {
@@ -163,12 +315,13 @@ const ButtonView = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref)
163
315
  // Base button props
164
316
  const baseProps: Partial<MuiButtonProps> = {
165
317
  variant: getMuiVariant(),
166
- color: getMuiColor(),
167
318
  size: buttonSize,
168
319
  disabled: disabled || loading,
169
320
  fullWidth,
170
321
  ...restProps,
171
322
  sx: {
323
+ // Apply CSS theme variable colors
324
+ ...getColorStyles(),
172
325
  // Ensure consistent text transform
173
326
  textTransform: 'none',
174
327
  // Loading state adjustments
@@ -177,6 +330,7 @@ const ButtonView = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref)
177
330
  marginRight: 1,
178
331
  },
179
332
  }),
333
+ // Merge user-provided sx prop last to allow overrides
180
334
  ...(restProps.sx || {}),
181
335
  },
182
336
  startIcon: loading ? (
@@ -229,6 +383,26 @@ export const Button: SerializableComponent<ButtonProps> = createSerializableView
229
383
  role: 'view',
230
384
  View: ButtonView,
231
385
  // Button component uses default react-children strategy for potential child content
386
+ finalize: (props: ButtonProps) => {
387
+ // Transform icon string names to React icon components
388
+ const transformedProps = { ...props };
389
+
390
+ if (typeof (props as any).icon === 'string') {
391
+ const iconComponent = getIconComponent((props as any).icon);
392
+ if (iconComponent) {
393
+ transformedProps.icon = iconComponent;
394
+ }
395
+ }
396
+
397
+ if (typeof (props as any).endIcon === 'string') {
398
+ const endIconComponent = getIconComponent((props as any).endIcon);
399
+ if (endIconComponent) {
400
+ transformedProps.endIcon = endIconComponent;
401
+ }
402
+ }
403
+
404
+ return transformedProps;
405
+ }
232
406
  });
233
407
 
234
408
  // Register HTML patterns that Button component can handle
@@ -13,8 +13,8 @@ export interface MenuItem {
13
13
  id: string;
14
14
  /** Display label for the menu item */
15
15
  label: string;
16
- /** Icon component or JSX element to display */
17
- icon?: React.ReactNode;
16
+ /** Icon name (string) or icon component (React.ReactNode) to display */
17
+ icon?: string | React.ReactNode;
18
18
  /** Click handler for the menu item */
19
19
  onClick?: () => void;
20
20
  /** External link URL (if this is a link) */
@@ -242,7 +242,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
242
242
  const surfaceMain = getCSSCustomProperty('--theme-surface') || (isDark ? '#1e1e1e' : '#ffffff');
243
243
 
244
244
  const textPrimary = getCSSCustomProperty('--theme-text-primary') || (isDark ? '#ffffff' : '#000000');
245
- const textSecondary = getCSSCustomProperty('--theme-text-secondary') || (isDark ? '#000000' : '#ffffff');
245
+ const textSecondary = getCSSCustomProperty('--theme-text-secondary') || (isDark ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.6)');
246
246
  const textDisabled = getCSSCustomProperty('--theme-on-disabled') || (isDark ? 'rgba(255, 255, 255, 0.38)' : 'rgba(0, 0, 0, 0.38)');
247
247
 
248
248
  return createTheme({
@@ -33,6 +33,17 @@ export class ButtonModel extends ViewSchema {
33
33
  @IsIn(['primary', 'secondary', 'outlined', 'text', 'contained'])
34
34
  variant?: 'primary' | 'secondary' | 'outlined' | 'text' | 'contained';
35
35
 
36
+ @Field()
37
+ @Editor({
38
+ field_type: FieldType.SELECT,
39
+ label: 'Button Color',
40
+ description: 'Color theme for the button (overrides variant color)'
41
+ })
42
+ @IsOptional()
43
+ @IsString()
44
+ @IsIn(['primary', 'secondary', 'error', 'warning', 'info', 'success'])
45
+ color?: 'primary' | 'secondary' | 'error' | 'warning' | 'info' | 'success';
46
+
36
47
  @Field({ defaultValue: 'medium' })
37
48
  @Editor({
38
49
  field_type: FieldType.SELECT,
@@ -95,6 +106,28 @@ export class ButtonModel extends ViewSchema {
95
106
  @IsOptional()
96
107
  @IsBoolean()
97
108
  fullWidth?: boolean;
109
+
110
+ @Field()
111
+ @Editor({
112
+ field_type: FieldType.TEXT,
113
+ label: 'Icon',
114
+ description: 'Icon name to display before button text (e.g., "home", "download", "settings")',
115
+ placeholder: 'Icon name...'
116
+ })
117
+ @IsOptional()
118
+ @IsString()
119
+ icon?: string;
120
+
121
+ @Field()
122
+ @Editor({
123
+ field_type: FieldType.TEXT,
124
+ label: 'End Icon',
125
+ description: 'Icon name to display after button text',
126
+ placeholder: 'Icon name...'
127
+ })
128
+ @IsOptional()
129
+ @IsString()
130
+ endIcon?: string;
98
131
  }
99
132
 
100
133
  export default ButtonModel;
@@ -516,7 +516,7 @@ export class ViewSchema extends Model {
516
516
  field_type: FieldType.TEXTAREA,
517
517
  label: 'Click Handler',
518
518
  description: 'JavaScript function for click events',
519
- placeholder: 'function(event) { console.log("clicked"); }'
519
+ placeholder: 'function(event) { console.debug("clicked"); }'
520
520
  })
521
521
  @IsOptional()
522
522
  @IsString()
@@ -85,7 +85,6 @@ export class ComponentTransformer {
85
85
  console.warn(`Component '${tagName}' is already registered. Overwriting existing registration.`);
86
86
  }
87
87
 
88
- console.log(`TEST: Registering component: ${tagName} (version ${version})`);
89
88
  componentRegistry.set(tagName, componentClass);
90
89
 
91
90
  // Register HTML patterns if component supports them
@@ -210,14 +209,10 @@ export class ComponentTransformer {
210
209
  if (typeof componentType === 'function') {
211
210
  const tagName = ComponentTransformer.findTagNameForComponent(componentType);
212
211
  if (tagName) {
213
- console.log(`TEST: Serializing component instance of ${tagName}:`, element.props);
214
212
  const componentClass = componentRegistry.get(tagName)!;
215
213
 
216
214
  let serializedData: any = null;
217
- console.log(`TEST: Element type:`, typeof (element as any).toJson);
218
- console.log(`TEST: Component type:`, typeof (componentClass as any).toJson);
219
215
  if (typeof (componentClass as any).toJson === 'function') {
220
- console.log(`TEST: Serializing via component toJson method ${tagName}:`, element.props);
221
216
  serializedData = (componentClass as any).toJson(element.props);
222
217
  }
223
218
 
@@ -231,7 +226,7 @@ export class ComponentTransformer {
231
226
  ...key,
232
227
  ...serializedData
233
228
  };
234
- console.log(`TEST: Serialized component ${tagName}:`, result);
229
+
235
230
  return result;
236
231
  }
237
232
  } else if (strictMode) {
@@ -251,8 +246,7 @@ export class ComponentTransformer {
251
246
  version: FALLBACK_VERSION,
252
247
  data: ReactNodeTransformer.serialize(node)
253
248
  };
254
- console.log(`TEST: Serialized unregistered node as ${FALLBACK_TAG}:`, result, node);
255
- console.log(`TEST: Component Registry:`, componentRegistry);
249
+
256
250
  return result;
257
251
  }
258
252
 
@@ -45,7 +45,6 @@ export class ReactNodeTransformer {
45
45
  // Handle React elements
46
46
  if (isValidElement(node)) {
47
47
  const element = node as ReactElement;
48
- console.log(`TEST: isValidElement:`, element);
49
48
 
50
49
  const comp: any = element.type;
51
50
  const rawName =
@@ -176,7 +175,7 @@ export class ReactNodeTransformer {
176
175
  // Handle HTML elements
177
176
  if (typeof elementType === 'string') {
178
177
  const deserializedProps = ReactNodeTransformer.deserializeProps(props);
179
- console.log(`TEST: Create Element:`, elementType, deserializedProps);
178
+
180
179
  return createElement(elementType, { key, ...deserializedProps });
181
180
  }
182
181
  } catch (error) {
@@ -58,7 +58,7 @@ function MyApp() {
58
58
  &lt;ProductCard dataSource="products.featured" /&gt;
59
59
  &lt;/QwickApp&gt;</code></pre>
60
60
 
61
- <p>You can also use inline code like <code>console.log('hello')</code> within paragraphs.</p>
61
+ <p>You can also use inline code like <code>console.debug('hello')</code> within paragraphs.</p>
62
62
 
63
63
  <h2>Complex Multi-line Code</h2>
64
64
  <code>
@@ -198,12 +198,12 @@ function InteractiveChoiceExample({
198
198
  const newOptions = [...options];
199
199
  newOptions[optionIndex] = value;
200
200
  setOptions(newOptions);
201
- console.log('Option changed:', { optionIndex, value, allOptions: newOptions });
201
+ console.debug('Option changed:', { optionIndex, value, allOptions: newOptions });
202
202
  };
203
203
 
204
204
  const handleAddOption = () => {
205
205
  setOptions([...options, '']);
206
- console.log('Added new option, total:', options.length + 1);
206
+ console.debug('Added new option, total:', options.length + 1);
207
207
  };
208
208
 
209
209
  return (