@qwickapps/react-framework 1.3.4 → 1.3.5
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/README.md +7 -0
- package/dist/components/blocks/Content.d.ts.map +1 -1
- package/dist/components/blocks/ProductCard.d.ts.map +1 -1
- package/dist/contexts/ThemeContext.d.ts.map +1 -1
- package/dist/index.esm.js +87 -99
- package/dist/index.js +87 -99
- package/package.json +1 -1
- package/src/components/blocks/Content.tsx +25 -77
- package/src/components/blocks/ProductCard.tsx +50 -51
- package/src/contexts/ThemeContext.tsx +1 -2
package/README.md
CHANGED
|
@@ -4,6 +4,13 @@ A complete React framework for building modern, responsive applications with int
|
|
|
4
4
|
|
|
5
5
|
## What's New
|
|
6
6
|
|
|
7
|
+
### September 4, 2025 - Stability & Inline Wrapper Guard
|
|
8
|
+
|
|
9
|
+
- **ProductCard Stability Fix**: Removed inline wrapper React components that caused subtree remounts and potential focus/state loss; replaced with stable JSX fragments.
|
|
10
|
+
- **Preventive Lint Rule**: Repo now enforces a custom `no-inline-component-wrapper` ESLint rule (scoped rollout) to block reintroduction of the remount pattern.
|
|
11
|
+
- **Internal Refactor Only**: No public API changes; safe patch update.
|
|
12
|
+
|
|
13
|
+
|
|
7
14
|
### September 2, 2025 - Built-in Error Handling & Accessibility
|
|
8
15
|
- **Automatic Error Boundaries**: QwickApp now automatically wraps all content with ErrorBoundary for robust error handling
|
|
9
16
|
- **Built-in Accessibility**: AccessibilityProvider automatically included for WCAG 2.1 AA compliance
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Content.d.ts","sourceRoot":"","sources":["../../../src/components/blocks/Content.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGhE,OAAO,YAAY,MAAM,6BAA6B,CAAC;AAMvD,KAAK,gBAAgB,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;AAEjD,MAAM,WAAW,YAAa,SAAQ,gBAAgB,EAAE,eAAe;CAAG;
|
|
1
|
+
{"version":3,"file":"Content.d.ts","sourceRoot":"","sources":["../../../src/components/blocks/Content.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGhE,OAAO,YAAY,MAAM,6BAA6B,CAAC;AAMvD,KAAK,gBAAgB,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;AAEjD,MAAM,WAAW,YAAa,SAAQ,gBAAgB,EAAE,eAAe;CAAG;AAwI1E,iBAAS,OAAO,CAAC,KAAK,EAAE,YAAY,kDA6CnC;AAED,eAAe,OAAO,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductCard.d.ts","sourceRoot":"","sources":["../../../src/components/blocks/ProductCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAoD,aAAa,EAAE,MAAM,aAAa,CAAC;AAI9F,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAAC;IAC5C,KAAK,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,UAAU,oBAAqB,SAAQ,aAAa;IAClD,mBAAmB;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB;IACnB,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACjC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC9B,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,gCAAgC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,+CAA+C;IAC/C,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB,EAAE,eAAe;CAAG;
|
|
1
|
+
{"version":3,"file":"ProductCard.d.ts","sourceRoot":"","sources":["../../../src/components/blocks/ProductCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAoD,aAAa,EAAE,MAAM,aAAa,CAAC;AAI9F,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAAC;IAC5C,KAAK,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,UAAU,oBAAqB,SAAQ,aAAa;IAClD,mBAAmB;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB;IACnB,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACjC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC9B,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,gCAAgC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,+CAA+C;IAC/C,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB,EAAE,eAAe;CAAG;AA+UlF;;;GAGG;AACH,iBAAS,WAAW,CAAC,KAAK,EAAE,gBAAgB,kDAmE3C;AAED,eAAe,WAAW,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeContext.d.ts","sourceRoot":"","sources":["../../src/contexts/ThemeContext.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,EAAkD,SAAS,EAAE,MAAM,OAAO,CAAC;AACzF,OAAO,EAAe,KAAK,EAAqC,MAAM,sBAAsB,CAAC;AAW7F,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AACpD,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAChC,2FAA2F;IAC3F,YAAY,EAAE,SAAS,CAAC;IACxB,6DAA6D;IAC7D,cAAc,EAAE,SAAS,GAAG,IAAI,CAAC;IACjC,yEAAyE;IACzE,eAAe,EAAE,eAAe,CAAC;IACjC,2BAA2B;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,6CAA6C;IAC7C,MAAM,EAAE,OAAO,CAAC;IAChB,wDAAwD;IACxD,YAAY,EAAE,OAAO,CAAC;IAGtB,uEAAuE;IACvE,iBAAiB,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC9C,+DAA+D;IAC/D,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,iCAAiC;IACjC,iBAAiB,EAAE,MAAM,SAAS,GAAG,IAAI,CAAC;IAG1C,kCAAkC;IAClC,eAAe,EAAE,MAAM,SAAS,CAAC;IACjC,2EAA2E;IAC3E,eAAe,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC;IAC9B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAKD,eAAO,MAAM,QAAQ,QAAO,iBAM3B,CAAC;AAoBF,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,
|
|
1
|
+
{"version":3,"file":"ThemeContext.d.ts","sourceRoot":"","sources":["../../src/contexts/ThemeContext.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,EAAkD,SAAS,EAAE,MAAM,OAAO,CAAC;AACzF,OAAO,EAAe,KAAK,EAAqC,MAAM,sBAAsB,CAAC;AAW7F,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AACpD,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAChC,2FAA2F;IAC3F,YAAY,EAAE,SAAS,CAAC;IACxB,6DAA6D;IAC7D,cAAc,EAAE,SAAS,GAAG,IAAI,CAAC;IACjC,yEAAyE;IACzE,eAAe,EAAE,eAAe,CAAC;IACjC,2BAA2B;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,6CAA6C;IAC7C,MAAM,EAAE,OAAO,CAAC;IAChB,wDAAwD;IACxD,YAAY,EAAE,OAAO,CAAC;IAGtB,uEAAuE;IACvE,iBAAiB,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC9C,+DAA+D;IAC/D,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,iCAAiC;IACjC,iBAAiB,EAAE,MAAM,SAAS,GAAG,IAAI,CAAC;IAG1C,kCAAkC;IAClC,eAAe,EAAE,MAAM,SAAS,CAAC;IACjC,2EAA2E;IAC3E,eAAe,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC;IAC9B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAKD,eAAO,MAAM,QAAQ,QAAO,iBAM3B,CAAC;AAoBF,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAyQtD,CAAC"}
|
package/dist/index.esm.js
CHANGED
|
@@ -13760,72 +13760,62 @@ function ContentView({
|
|
|
13760
13760
|
return 3;
|
|
13761
13761
|
}
|
|
13762
13762
|
};
|
|
13763
|
-
//
|
|
13764
|
-
const
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
13781
|
-
return jsx(Paper, {
|
|
13782
|
-
elevation: 4,
|
|
13783
|
-
...htmlProps,
|
|
13784
|
-
...otherProps,
|
|
13785
|
-
sx: {
|
|
13786
|
-
...commonSx,
|
|
13787
|
-
backgroundColor: theme.palette.background.paper
|
|
13788
|
-
},
|
|
13789
|
-
children: children
|
|
13790
|
-
});
|
|
13791
|
-
case 'outlined':
|
|
13792
|
-
return jsx(Paper, {
|
|
13793
|
-
variant: "outlined",
|
|
13794
|
-
elevation: 0,
|
|
13795
|
-
...htmlProps,
|
|
13796
|
-
...otherProps,
|
|
13797
|
-
sx: {
|
|
13798
|
-
...commonSx,
|
|
13799
|
-
backgroundColor: 'var(--theme-surface)',
|
|
13800
|
-
borderColor: 'var(--theme-border-main)',
|
|
13801
|
-
// Use theme border color
|
|
13802
|
-
borderWidth: 1,
|
|
13803
|
-
borderStyle: 'solid'
|
|
13804
|
-
},
|
|
13805
|
-
children: children
|
|
13806
|
-
});
|
|
13807
|
-
case 'filled':
|
|
13808
|
-
return jsx(Box, {
|
|
13809
|
-
...htmlProps,
|
|
13810
|
-
...otherProps,
|
|
13811
|
-
sx: {
|
|
13812
|
-
...commonSx,
|
|
13813
|
-
backgroundColor: 'var(--theme-surface-variant)',
|
|
13814
|
-
// Use theme surface variant
|
|
13815
|
-
borderRadius: 1
|
|
13816
|
-
},
|
|
13817
|
-
children: children
|
|
13818
|
-
});
|
|
13819
|
-
default:
|
|
13820
|
-
return jsx(Box, {
|
|
13821
|
-
...htmlProps,
|
|
13822
|
-
...otherProps,
|
|
13823
|
-
sx: commonSx,
|
|
13824
|
-
children: children
|
|
13825
|
-
});
|
|
13826
|
-
}
|
|
13763
|
+
// Compute stable wrapper element (avoid recreating component type each render which caused remount & focus loss)
|
|
13764
|
+
const paddingValue = getPadding();
|
|
13765
|
+
const mappedMaxWidth = mapToMUIBreakpoint(contentMaxWidth === 'false' ? false : contentMaxWidth);
|
|
13766
|
+
const commonSx = {
|
|
13767
|
+
textAlign: centered ? 'center' : 'left',
|
|
13768
|
+
maxWidth: mappedMaxWidth !== false ? theme.breakpoints.values[mappedMaxWidth] : '100%',
|
|
13769
|
+
width: '100%',
|
|
13770
|
+
...(centered && contentMaxWidth && {
|
|
13771
|
+
mx: 'auto'
|
|
13772
|
+
}),
|
|
13773
|
+
p: paddingValue,
|
|
13774
|
+
...styleProps.sx
|
|
13775
|
+
};
|
|
13776
|
+
let Wrapper = Box;
|
|
13777
|
+
let wrapperProps = {
|
|
13778
|
+
...htmlProps,
|
|
13779
|
+
...otherProps,
|
|
13780
|
+
sx: commonSx
|
|
13827
13781
|
};
|
|
13828
|
-
|
|
13782
|
+
if (variant === 'elevated') {
|
|
13783
|
+
Wrapper = Paper;
|
|
13784
|
+
wrapperProps = {
|
|
13785
|
+
...wrapperProps,
|
|
13786
|
+
elevation: 4,
|
|
13787
|
+
sx: {
|
|
13788
|
+
...commonSx,
|
|
13789
|
+
backgroundColor: theme.palette.background.paper
|
|
13790
|
+
}
|
|
13791
|
+
};
|
|
13792
|
+
} else if (variant === 'outlined') {
|
|
13793
|
+
Wrapper = Paper;
|
|
13794
|
+
wrapperProps = {
|
|
13795
|
+
...wrapperProps,
|
|
13796
|
+
variant: 'outlined',
|
|
13797
|
+
elevation: 0,
|
|
13798
|
+
sx: {
|
|
13799
|
+
...commonSx,
|
|
13800
|
+
backgroundColor: 'var(--theme-surface)',
|
|
13801
|
+
borderColor: 'var(--theme-border-main)',
|
|
13802
|
+
borderWidth: 1,
|
|
13803
|
+
borderStyle: 'solid'
|
|
13804
|
+
}
|
|
13805
|
+
};
|
|
13806
|
+
} else if (variant === 'filled') {
|
|
13807
|
+
Wrapper = Box;
|
|
13808
|
+
wrapperProps = {
|
|
13809
|
+
...wrapperProps,
|
|
13810
|
+
sx: {
|
|
13811
|
+
...commonSx,
|
|
13812
|
+
backgroundColor: 'var(--theme-surface-variant)',
|
|
13813
|
+
borderRadius: 1
|
|
13814
|
+
}
|
|
13815
|
+
};
|
|
13816
|
+
}
|
|
13817
|
+
return jsx(Wrapper, {
|
|
13818
|
+
...wrapperProps,
|
|
13829
13819
|
children: jsxs(Stack, {
|
|
13830
13820
|
spacing: 2,
|
|
13831
13821
|
children: [(title || subtitle) && jsxs(Box, {
|
|
@@ -17137,7 +17127,9 @@ function ProductCardView({
|
|
|
17137
17127
|
return actions;
|
|
17138
17128
|
};
|
|
17139
17129
|
const displayActions = actions || getDefaultActions();
|
|
17140
|
-
|
|
17130
|
+
// INLINE WRAPPERS REFACTORED: Instead of inline component declarations (which cause remounts),
|
|
17131
|
+
// compute JSX fragments via plain variables/functions and inject directly.
|
|
17132
|
+
const featuresListElement = (() => {
|
|
17141
17133
|
const featuresToShow = variant === 'compact' ? (product.features || []).slice(0, maxFeaturesCompact) : product.features || [];
|
|
17142
17134
|
return jsxs(Box, {
|
|
17143
17135
|
sx: {
|
|
@@ -17197,40 +17189,37 @@ function ProductCardView({
|
|
|
17197
17189
|
})]
|
|
17198
17190
|
})]
|
|
17199
17191
|
});
|
|
17200
|
-
};
|
|
17201
|
-
const
|
|
17202
|
-
|
|
17203
|
-
|
|
17192
|
+
})();
|
|
17193
|
+
const technologiesSectionElement = !showTechnologies || variant === 'compact' ? null : jsxs(Box, {
|
|
17194
|
+
sx: {
|
|
17195
|
+
mb: 3
|
|
17196
|
+
},
|
|
17197
|
+
children: [jsx(Typography, {
|
|
17198
|
+
variant: "h6",
|
|
17199
|
+
component: "h4",
|
|
17204
17200
|
sx: {
|
|
17205
|
-
mb:
|
|
17201
|
+
mb: 1.5,
|
|
17202
|
+
fontSize: '1rem',
|
|
17203
|
+
fontWeight: 600
|
|
17206
17204
|
},
|
|
17207
|
-
children:
|
|
17208
|
-
|
|
17209
|
-
|
|
17210
|
-
|
|
17211
|
-
|
|
17212
|
-
|
|
17213
|
-
|
|
17214
|
-
|
|
17215
|
-
|
|
17216
|
-
|
|
17205
|
+
children: "Technologies:"
|
|
17206
|
+
}), jsx(Box, {
|
|
17207
|
+
sx: {
|
|
17208
|
+
display: 'flex',
|
|
17209
|
+
flexWrap: 'wrap',
|
|
17210
|
+
gap: 1
|
|
17211
|
+
},
|
|
17212
|
+
children: product.technologies.map((tech, index) => jsx(Chip, {
|
|
17213
|
+
label: tech,
|
|
17214
|
+
variant: "outlined",
|
|
17215
|
+
size: "small",
|
|
17217
17216
|
sx: {
|
|
17218
|
-
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
variant: "outlined",
|
|
17225
|
-
size: "small",
|
|
17226
|
-
sx: {
|
|
17227
|
-
fontSize: '0.8rem',
|
|
17228
|
-
fontWeight: 500
|
|
17229
|
-
}
|
|
17230
|
-
}, index))
|
|
17231
|
-
})]
|
|
17232
|
-
});
|
|
17233
|
-
};
|
|
17217
|
+
fontSize: '0.8rem',
|
|
17218
|
+
fontWeight: 500
|
|
17219
|
+
}
|
|
17220
|
+
}, index))
|
|
17221
|
+
})]
|
|
17222
|
+
});
|
|
17234
17223
|
return jsxs(Box, {
|
|
17235
17224
|
className: styleProps.className || "product-card",
|
|
17236
17225
|
onClick: htmlProps.onClick || (variant === 'compact' ? handleProductClick : undefined),
|
|
@@ -17336,7 +17325,7 @@ function ProductCardView({
|
|
|
17336
17325
|
},
|
|
17337
17326
|
children: variant === 'detailed' ? product.description : product.shortDescription || product.description
|
|
17338
17327
|
})]
|
|
17339
|
-
}),
|
|
17328
|
+
}), featuresListElement, technologiesSectionElement, jsx(Box, {
|
|
17340
17329
|
sx: {
|
|
17341
17330
|
display: 'flex',
|
|
17342
17331
|
gap: 1.5,
|
|
@@ -18197,7 +18186,6 @@ const ThemeProvider = ({
|
|
|
18197
18186
|
}
|
|
18198
18187
|
}
|
|
18199
18188
|
});
|
|
18200
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
18201
18189
|
}, [actualThemeMode, paletteChangeCounter]); // paletteChangeCounter is needed to force theme rebuild // Re-create theme when palette changes
|
|
18202
18190
|
// Theme preference management methods
|
|
18203
18191
|
const setPreferredTheme = theme => {
|
package/dist/index.js
CHANGED
|
@@ -13762,72 +13762,62 @@ function ContentView({
|
|
|
13762
13762
|
return 3;
|
|
13763
13763
|
}
|
|
13764
13764
|
};
|
|
13765
|
-
//
|
|
13766
|
-
const
|
|
13767
|
-
|
|
13768
|
-
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
13781
|
-
|
|
13782
|
-
|
|
13783
|
-
return jsxRuntime.jsx(material.Paper, {
|
|
13784
|
-
elevation: 4,
|
|
13785
|
-
...htmlProps,
|
|
13786
|
-
...otherProps,
|
|
13787
|
-
sx: {
|
|
13788
|
-
...commonSx,
|
|
13789
|
-
backgroundColor: theme.palette.background.paper
|
|
13790
|
-
},
|
|
13791
|
-
children: children
|
|
13792
|
-
});
|
|
13793
|
-
case 'outlined':
|
|
13794
|
-
return jsxRuntime.jsx(material.Paper, {
|
|
13795
|
-
variant: "outlined",
|
|
13796
|
-
elevation: 0,
|
|
13797
|
-
...htmlProps,
|
|
13798
|
-
...otherProps,
|
|
13799
|
-
sx: {
|
|
13800
|
-
...commonSx,
|
|
13801
|
-
backgroundColor: 'var(--theme-surface)',
|
|
13802
|
-
borderColor: 'var(--theme-border-main)',
|
|
13803
|
-
// Use theme border color
|
|
13804
|
-
borderWidth: 1,
|
|
13805
|
-
borderStyle: 'solid'
|
|
13806
|
-
},
|
|
13807
|
-
children: children
|
|
13808
|
-
});
|
|
13809
|
-
case 'filled':
|
|
13810
|
-
return jsxRuntime.jsx(material.Box, {
|
|
13811
|
-
...htmlProps,
|
|
13812
|
-
...otherProps,
|
|
13813
|
-
sx: {
|
|
13814
|
-
...commonSx,
|
|
13815
|
-
backgroundColor: 'var(--theme-surface-variant)',
|
|
13816
|
-
// Use theme surface variant
|
|
13817
|
-
borderRadius: 1
|
|
13818
|
-
},
|
|
13819
|
-
children: children
|
|
13820
|
-
});
|
|
13821
|
-
default:
|
|
13822
|
-
return jsxRuntime.jsx(material.Box, {
|
|
13823
|
-
...htmlProps,
|
|
13824
|
-
...otherProps,
|
|
13825
|
-
sx: commonSx,
|
|
13826
|
-
children: children
|
|
13827
|
-
});
|
|
13828
|
-
}
|
|
13765
|
+
// Compute stable wrapper element (avoid recreating component type each render which caused remount & focus loss)
|
|
13766
|
+
const paddingValue = getPadding();
|
|
13767
|
+
const mappedMaxWidth = mapToMUIBreakpoint(contentMaxWidth === 'false' ? false : contentMaxWidth);
|
|
13768
|
+
const commonSx = {
|
|
13769
|
+
textAlign: centered ? 'center' : 'left',
|
|
13770
|
+
maxWidth: mappedMaxWidth !== false ? theme.breakpoints.values[mappedMaxWidth] : '100%',
|
|
13771
|
+
width: '100%',
|
|
13772
|
+
...(centered && contentMaxWidth && {
|
|
13773
|
+
mx: 'auto'
|
|
13774
|
+
}),
|
|
13775
|
+
p: paddingValue,
|
|
13776
|
+
...styleProps.sx
|
|
13777
|
+
};
|
|
13778
|
+
let Wrapper = material.Box;
|
|
13779
|
+
let wrapperProps = {
|
|
13780
|
+
...htmlProps,
|
|
13781
|
+
...otherProps,
|
|
13782
|
+
sx: commonSx
|
|
13829
13783
|
};
|
|
13830
|
-
|
|
13784
|
+
if (variant === 'elevated') {
|
|
13785
|
+
Wrapper = material.Paper;
|
|
13786
|
+
wrapperProps = {
|
|
13787
|
+
...wrapperProps,
|
|
13788
|
+
elevation: 4,
|
|
13789
|
+
sx: {
|
|
13790
|
+
...commonSx,
|
|
13791
|
+
backgroundColor: theme.palette.background.paper
|
|
13792
|
+
}
|
|
13793
|
+
};
|
|
13794
|
+
} else if (variant === 'outlined') {
|
|
13795
|
+
Wrapper = material.Paper;
|
|
13796
|
+
wrapperProps = {
|
|
13797
|
+
...wrapperProps,
|
|
13798
|
+
variant: 'outlined',
|
|
13799
|
+
elevation: 0,
|
|
13800
|
+
sx: {
|
|
13801
|
+
...commonSx,
|
|
13802
|
+
backgroundColor: 'var(--theme-surface)',
|
|
13803
|
+
borderColor: 'var(--theme-border-main)',
|
|
13804
|
+
borderWidth: 1,
|
|
13805
|
+
borderStyle: 'solid'
|
|
13806
|
+
}
|
|
13807
|
+
};
|
|
13808
|
+
} else if (variant === 'filled') {
|
|
13809
|
+
Wrapper = material.Box;
|
|
13810
|
+
wrapperProps = {
|
|
13811
|
+
...wrapperProps,
|
|
13812
|
+
sx: {
|
|
13813
|
+
...commonSx,
|
|
13814
|
+
backgroundColor: 'var(--theme-surface-variant)',
|
|
13815
|
+
borderRadius: 1
|
|
13816
|
+
}
|
|
13817
|
+
};
|
|
13818
|
+
}
|
|
13819
|
+
return jsxRuntime.jsx(Wrapper, {
|
|
13820
|
+
...wrapperProps,
|
|
13831
13821
|
children: jsxRuntime.jsxs(material.Stack, {
|
|
13832
13822
|
spacing: 2,
|
|
13833
13823
|
children: [(title || subtitle) && jsxRuntime.jsxs(material.Box, {
|
|
@@ -17139,7 +17129,9 @@ function ProductCardView({
|
|
|
17139
17129
|
return actions;
|
|
17140
17130
|
};
|
|
17141
17131
|
const displayActions = actions || getDefaultActions();
|
|
17142
|
-
|
|
17132
|
+
// INLINE WRAPPERS REFACTORED: Instead of inline component declarations (which cause remounts),
|
|
17133
|
+
// compute JSX fragments via plain variables/functions and inject directly.
|
|
17134
|
+
const featuresListElement = (() => {
|
|
17143
17135
|
const featuresToShow = variant === 'compact' ? (product.features || []).slice(0, maxFeaturesCompact) : product.features || [];
|
|
17144
17136
|
return jsxRuntime.jsxs(material.Box, {
|
|
17145
17137
|
sx: {
|
|
@@ -17199,40 +17191,37 @@ function ProductCardView({
|
|
|
17199
17191
|
})]
|
|
17200
17192
|
})]
|
|
17201
17193
|
});
|
|
17202
|
-
};
|
|
17203
|
-
const
|
|
17204
|
-
|
|
17205
|
-
|
|
17194
|
+
})();
|
|
17195
|
+
const technologiesSectionElement = !showTechnologies || variant === 'compact' ? null : jsxRuntime.jsxs(material.Box, {
|
|
17196
|
+
sx: {
|
|
17197
|
+
mb: 3
|
|
17198
|
+
},
|
|
17199
|
+
children: [jsxRuntime.jsx(material.Typography, {
|
|
17200
|
+
variant: "h6",
|
|
17201
|
+
component: "h4",
|
|
17206
17202
|
sx: {
|
|
17207
|
-
mb:
|
|
17203
|
+
mb: 1.5,
|
|
17204
|
+
fontSize: '1rem',
|
|
17205
|
+
fontWeight: 600
|
|
17208
17206
|
},
|
|
17209
|
-
children:
|
|
17210
|
-
|
|
17211
|
-
|
|
17212
|
-
|
|
17213
|
-
|
|
17214
|
-
|
|
17215
|
-
|
|
17216
|
-
|
|
17217
|
-
|
|
17218
|
-
|
|
17207
|
+
children: "Technologies:"
|
|
17208
|
+
}), jsxRuntime.jsx(material.Box, {
|
|
17209
|
+
sx: {
|
|
17210
|
+
display: 'flex',
|
|
17211
|
+
flexWrap: 'wrap',
|
|
17212
|
+
gap: 1
|
|
17213
|
+
},
|
|
17214
|
+
children: product.technologies.map((tech, index) => jsxRuntime.jsx(material.Chip, {
|
|
17215
|
+
label: tech,
|
|
17216
|
+
variant: "outlined",
|
|
17217
|
+
size: "small",
|
|
17219
17218
|
sx: {
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
|
|
17225
|
-
|
|
17226
|
-
variant: "outlined",
|
|
17227
|
-
size: "small",
|
|
17228
|
-
sx: {
|
|
17229
|
-
fontSize: '0.8rem',
|
|
17230
|
-
fontWeight: 500
|
|
17231
|
-
}
|
|
17232
|
-
}, index))
|
|
17233
|
-
})]
|
|
17234
|
-
});
|
|
17235
|
-
};
|
|
17219
|
+
fontSize: '0.8rem',
|
|
17220
|
+
fontWeight: 500
|
|
17221
|
+
}
|
|
17222
|
+
}, index))
|
|
17223
|
+
})]
|
|
17224
|
+
});
|
|
17236
17225
|
return jsxRuntime.jsxs(material.Box, {
|
|
17237
17226
|
className: styleProps.className || "product-card",
|
|
17238
17227
|
onClick: htmlProps.onClick || (variant === 'compact' ? handleProductClick : undefined),
|
|
@@ -17338,7 +17327,7 @@ function ProductCardView({
|
|
|
17338
17327
|
},
|
|
17339
17328
|
children: variant === 'detailed' ? product.description : product.shortDescription || product.description
|
|
17340
17329
|
})]
|
|
17341
|
-
}),
|
|
17330
|
+
}), featuresListElement, technologiesSectionElement, jsxRuntime.jsx(material.Box, {
|
|
17342
17331
|
sx: {
|
|
17343
17332
|
display: 'flex',
|
|
17344
17333
|
gap: 1.5,
|
|
@@ -18199,7 +18188,6 @@ const ThemeProvider = ({
|
|
|
18199
18188
|
}
|
|
18200
18189
|
}
|
|
18201
18190
|
});
|
|
18202
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
18203
18191
|
}, [actualThemeMode, paletteChangeCounter]); // paletteChangeCounter is needed to force theme rebuild // Re-create theme when palette changes
|
|
18204
18192
|
// Theme preference management methods
|
|
18205
18193
|
const setPreferredTheme = theme => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qwickapps/react-framework",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Complete React framework with responsive navigation, flexible layouts, theming system, and reusable components for building modern applications.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -61,85 +61,33 @@ function ContentView({
|
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
//
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
...(centered && contentMaxWidth && {
|
|
75
|
-
mx: 'auto',
|
|
76
|
-
}),
|
|
77
|
-
p: paddingValue,
|
|
78
|
-
...styleProps.sx,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
switch (variant) {
|
|
82
|
-
case 'elevated':
|
|
83
|
-
return (
|
|
84
|
-
<Paper
|
|
85
|
-
elevation={4}
|
|
86
|
-
{...htmlProps}
|
|
87
|
-
{...otherProps}
|
|
88
|
-
sx={{
|
|
89
|
-
...commonSx,
|
|
90
|
-
backgroundColor: theme.palette.background.paper,
|
|
91
|
-
}}
|
|
92
|
-
>
|
|
93
|
-
{children}
|
|
94
|
-
</Paper>
|
|
95
|
-
);
|
|
96
|
-
case 'outlined':
|
|
97
|
-
return (
|
|
98
|
-
<Paper
|
|
99
|
-
variant="outlined"
|
|
100
|
-
elevation={0}
|
|
101
|
-
{...htmlProps}
|
|
102
|
-
{...otherProps}
|
|
103
|
-
sx={{
|
|
104
|
-
...commonSx,
|
|
105
|
-
backgroundColor: 'var(--theme-surface)',
|
|
106
|
-
borderColor: 'var(--theme-border-main)', // Use theme border color
|
|
107
|
-
borderWidth: 1,
|
|
108
|
-
borderStyle: 'solid',
|
|
109
|
-
}}
|
|
110
|
-
>
|
|
111
|
-
{children}
|
|
112
|
-
</Paper>
|
|
113
|
-
);
|
|
114
|
-
case 'filled':
|
|
115
|
-
return (
|
|
116
|
-
<Box
|
|
117
|
-
{...htmlProps}
|
|
118
|
-
{...otherProps}
|
|
119
|
-
sx={{
|
|
120
|
-
...commonSx,
|
|
121
|
-
backgroundColor: 'var(--theme-surface-variant)', // Use theme surface variant
|
|
122
|
-
borderRadius: 1,
|
|
123
|
-
}}
|
|
124
|
-
>
|
|
125
|
-
{children}
|
|
126
|
-
</Box>
|
|
127
|
-
);
|
|
128
|
-
default:
|
|
129
|
-
return (
|
|
130
|
-
<Box
|
|
131
|
-
{...htmlProps}
|
|
132
|
-
{...otherProps}
|
|
133
|
-
sx={commonSx}
|
|
134
|
-
>
|
|
135
|
-
{children}
|
|
136
|
-
</Box>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
64
|
+
// Compute stable wrapper element (avoid recreating component type each render which caused remount & focus loss)
|
|
65
|
+
const paddingValue = getPadding();
|
|
66
|
+
const mappedMaxWidth = mapToMUIBreakpoint(contentMaxWidth === 'false' ? false : contentMaxWidth as BreakpointValue);
|
|
67
|
+
const commonSx: SxProps<Theme> = {
|
|
68
|
+
textAlign: centered ? 'center' : 'left',
|
|
69
|
+
maxWidth: mappedMaxWidth !== false ? theme.breakpoints.values[mappedMaxWidth] : '100%',
|
|
70
|
+
width: '100%',
|
|
71
|
+
...(centered && contentMaxWidth && { mx: 'auto' }),
|
|
72
|
+
p: paddingValue,
|
|
73
|
+
...styleProps.sx,
|
|
139
74
|
};
|
|
140
75
|
|
|
76
|
+
let Wrapper: React.ElementType = Box;
|
|
77
|
+
let wrapperProps: Record<string, any> = { ...htmlProps, ...otherProps, sx: commonSx };
|
|
78
|
+
if (variant === 'elevated') {
|
|
79
|
+
Wrapper = Paper;
|
|
80
|
+
wrapperProps = { ...wrapperProps, elevation: 4, sx: { ...commonSx, backgroundColor: theme.palette.background.paper } };
|
|
81
|
+
} else if (variant === 'outlined') {
|
|
82
|
+
Wrapper = Paper;
|
|
83
|
+
wrapperProps = { ...wrapperProps, variant: 'outlined', elevation: 0, sx: { ...commonSx, backgroundColor: 'var(--theme-surface)', borderColor: 'var(--theme-border-main)', borderWidth: 1, borderStyle: 'solid' } };
|
|
84
|
+
} else if (variant === 'filled') {
|
|
85
|
+
Wrapper = Box;
|
|
86
|
+
wrapperProps = { ...wrapperProps, sx: { ...commonSx, backgroundColor: 'var(--theme-surface-variant)', borderRadius: 1 } };
|
|
87
|
+
}
|
|
88
|
+
|
|
141
89
|
return (
|
|
142
|
-
<
|
|
90
|
+
<Wrapper {...wrapperProps}>
|
|
143
91
|
<Stack spacing={2}>
|
|
144
92
|
{/* Header */}
|
|
145
93
|
{(title || subtitle) && (
|
|
@@ -210,7 +158,7 @@ function ContentView({
|
|
|
210
158
|
</Stack>
|
|
211
159
|
)}
|
|
212
160
|
</Stack>
|
|
213
|
-
|
|
161
|
+
</Wrapper>
|
|
214
162
|
);
|
|
215
163
|
}
|
|
216
164
|
|
|
@@ -141,26 +141,29 @@ function ProductCardView({
|
|
|
141
141
|
|
|
142
142
|
const displayActions = actions || getDefaultActions();
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
// INLINE WRAPPERS REFACTORED: Instead of inline component declarations (which cause remounts),
|
|
145
|
+
// compute JSX fragments via plain variables/functions and inject directly.
|
|
146
|
+
|
|
147
|
+
const featuresListElement = (() => {
|
|
148
|
+
const featuresToShow = variant === 'compact'
|
|
146
149
|
? (product.features || []).slice(0, maxFeaturesCompact)
|
|
147
150
|
: (product.features || []);
|
|
148
151
|
|
|
149
152
|
return (
|
|
150
153
|
<Box sx={{ mb: variant === 'detailed' ? 2.5 : 2 }}>
|
|
151
|
-
<Typography
|
|
152
|
-
variant="h6"
|
|
154
|
+
<Typography
|
|
155
|
+
variant="h6"
|
|
153
156
|
component="h4"
|
|
154
|
-
sx={{
|
|
155
|
-
mb: 1.5,
|
|
157
|
+
sx={{
|
|
158
|
+
mb: 1.5,
|
|
156
159
|
fontSize: '1.1rem',
|
|
157
160
|
fontWeight: 600
|
|
158
161
|
}}
|
|
159
162
|
>
|
|
160
163
|
Key Features:
|
|
161
164
|
</Typography>
|
|
162
|
-
<Box component="ul" sx={{
|
|
163
|
-
m: 0,
|
|
165
|
+
<Box component="ul" sx={{
|
|
166
|
+
m: 0,
|
|
164
167
|
p: 0,
|
|
165
168
|
listStyle: 'none'
|
|
166
169
|
}}>
|
|
@@ -185,9 +188,9 @@ function ProductCardView({
|
|
|
185
188
|
flexShrink: 0
|
|
186
189
|
}}
|
|
187
190
|
/>
|
|
188
|
-
<Typography
|
|
189
|
-
variant="body2"
|
|
190
|
-
sx={{
|
|
191
|
+
<Typography
|
|
192
|
+
variant="body2"
|
|
193
|
+
sx={{
|
|
191
194
|
fontSize: '0.95rem',
|
|
192
195
|
lineHeight: 1.4,
|
|
193
196
|
opacity: 0.8
|
|
@@ -198,10 +201,10 @@ function ProductCardView({
|
|
|
198
201
|
</Box>
|
|
199
202
|
))}
|
|
200
203
|
{variant === 'compact' && (product.features || []).length > maxFeaturesCompact && (
|
|
201
|
-
<Typography
|
|
202
|
-
variant="body2"
|
|
204
|
+
<Typography
|
|
205
|
+
variant="body2"
|
|
203
206
|
color="primary"
|
|
204
|
-
sx={{
|
|
207
|
+
sx={{
|
|
205
208
|
fontSize: '0.9rem',
|
|
206
209
|
pl: 2.5,
|
|
207
210
|
fontWeight: 500
|
|
@@ -213,41 +216,37 @@ function ProductCardView({
|
|
|
213
216
|
</Box>
|
|
214
217
|
</Box>
|
|
215
218
|
);
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
const TechnologiesSection = () => {
|
|
219
|
-
if (!showTechnologies || variant === 'compact') return null;
|
|
219
|
+
})();
|
|
220
220
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
</Box>
|
|
221
|
+
const technologiesSectionElement = (!showTechnologies || variant === 'compact') ? null : (
|
|
222
|
+
<Box sx={{ mb: 3 }}>
|
|
223
|
+
<Typography
|
|
224
|
+
variant="h6"
|
|
225
|
+
component="h4"
|
|
226
|
+
sx={{
|
|
227
|
+
mb: 1.5,
|
|
228
|
+
fontSize: '1rem',
|
|
229
|
+
fontWeight: 600
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
232
|
+
Technologies:
|
|
233
|
+
</Typography>
|
|
234
|
+
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
|
235
|
+
{product.technologies.map((tech, index) => (
|
|
236
|
+
<Chip
|
|
237
|
+
key={index}
|
|
238
|
+
label={tech}
|
|
239
|
+
variant="outlined"
|
|
240
|
+
size="small"
|
|
241
|
+
sx={{
|
|
242
|
+
fontSize: '0.8rem',
|
|
243
|
+
fontWeight: 500
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
))}
|
|
248
247
|
</Box>
|
|
249
|
-
|
|
250
|
-
|
|
248
|
+
</Box>
|
|
249
|
+
);
|
|
251
250
|
|
|
252
251
|
return (
|
|
253
252
|
<Box
|
|
@@ -364,11 +363,11 @@ function ProductCardView({
|
|
|
364
363
|
</Typography>
|
|
365
364
|
</Box>
|
|
366
365
|
|
|
367
|
-
|
|
368
|
-
|
|
366
|
+
{/* Features */}
|
|
367
|
+
{featuresListElement}
|
|
369
368
|
|
|
370
|
-
|
|
371
|
-
|
|
369
|
+
{/* Technologies */}
|
|
370
|
+
{technologiesSectionElement}
|
|
372
371
|
|
|
373
372
|
{/* Action Buttons */}
|
|
374
373
|
<Box sx={{
|
|
@@ -302,8 +302,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
|
|
302
302
|
},
|
|
303
303
|
},
|
|
304
304
|
},
|
|
305
|
-
|
|
306
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
305
|
+
});
|
|
307
306
|
}, [actualThemeMode, paletteChangeCounter]); // paletteChangeCounter is needed to force theme rebuild // Re-create theme when palette changes
|
|
308
307
|
|
|
309
308
|
// Theme preference management methods
|