@qwickapps/react-framework 1.3.2 → 1.3.3

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 (33) hide show
  1. package/README.md +106 -0
  2. package/dist/components/AccessibilityProvider.d.ts +64 -0
  3. package/dist/components/AccessibilityProvider.d.ts.map +1 -0
  4. package/dist/components/Breadcrumbs.d.ts +39 -0
  5. package/dist/components/Breadcrumbs.d.ts.map +1 -0
  6. package/dist/components/ErrorBoundary.d.ts +39 -0
  7. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  8. package/dist/components/QwickApp.d.ts.map +1 -1
  9. package/dist/components/index.d.ts +3 -0
  10. package/dist/components/index.d.ts.map +1 -1
  11. package/dist/index.bundled.css +12 -0
  12. package/dist/index.esm.js +795 -12
  13. package/dist/index.js +800 -10
  14. package/package.json +1 -1
  15. package/src/components/AccessibilityProvider.tsx +466 -0
  16. package/src/components/Breadcrumbs.tsx +223 -0
  17. package/src/components/ErrorBoundary.tsx +216 -0
  18. package/src/components/QwickApp.tsx +17 -11
  19. package/src/components/__tests__/AccessibilityProvider.test.tsx +330 -0
  20. package/src/components/__tests__/Breadcrumbs.test.tsx +268 -0
  21. package/src/components/__tests__/ErrorBoundary.test.tsx +163 -0
  22. package/src/components/index.ts +3 -0
  23. package/src/stories/AccessibilityProvider.stories.tsx +284 -0
  24. package/src/stories/Breadcrumbs.stories.tsx +304 -0
  25. package/src/stories/ErrorBoundary.stories.tsx +159 -0
  26. package/dist/schemas/Builders.d.ts +0 -7
  27. package/dist/schemas/Builders.d.ts.map +0 -1
  28. package/dist/schemas/types.d.ts +0 -7
  29. package/dist/schemas/types.d.ts.map +0 -1
  30. package/dist/types/DataBinding.d.ts +0 -7
  31. package/dist/types/DataBinding.d.ts.map +0 -1
  32. package/dist/types/DataProvider.d.ts +0 -7
  33. package/dist/types/DataProvider.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -20581,6 +20581,605 @@ const Scaffold = ({
20581
20581
 
20582
20582
  loggers.menu;
20583
20583
 
20584
+ /**
20585
+ * Generic ErrorBoundary component for catching and handling React errors
20586
+ *
20587
+ * Features:
20588
+ * - Catches JavaScript errors anywhere in child component tree
20589
+ * - Displays fallback UI with retry functionality
20590
+ * - Shows error details in development mode
20591
+ * - Customizable error handling and fallback UI
20592
+ * - Automatic error logging
20593
+ */
20594
+ class ErrorBoundary extends React.Component {
20595
+ constructor(props) {
20596
+ super(props);
20597
+ this.handleRetry = () => {
20598
+ this.setState({
20599
+ hasError: false,
20600
+ error: null,
20601
+ errorInfo: null
20602
+ });
20603
+ };
20604
+ this.handleRefresh = () => {
20605
+ if (typeof window !== 'undefined') {
20606
+ window.location.reload();
20607
+ }
20608
+ };
20609
+ this.state = {
20610
+ hasError: false,
20611
+ error: null,
20612
+ errorInfo: null
20613
+ };
20614
+ }
20615
+ static getDerivedStateFromError(error) {
20616
+ // Update state so the next render will show the fallback UI
20617
+ return {
20618
+ hasError: true,
20619
+ error,
20620
+ errorInfo: null
20621
+ };
20622
+ }
20623
+ componentDidCatch(error, errorInfo) {
20624
+ // Log error details
20625
+ this.setState({
20626
+ error,
20627
+ errorInfo
20628
+ });
20629
+ // Log to console for debugging
20630
+ console.error('ErrorBoundary caught an error:', error, errorInfo);
20631
+ // Custom error handler
20632
+ if (this.props.onError) {
20633
+ this.props.onError(error, errorInfo);
20634
+ }
20635
+ // Send error to logging service if available
20636
+ if (typeof window !== 'undefined') {
20637
+ // @ts-ignore - Global error logging service
20638
+ if (window.qwickapps?.logError) {
20639
+ window.qwickapps.logError(error, errorInfo);
20640
+ }
20641
+ }
20642
+ }
20643
+ render() {
20644
+ if (this.state.hasError) {
20645
+ // Custom fallback UI
20646
+ if (this.props.fallback) {
20647
+ return this.props.fallback;
20648
+ }
20649
+ // Default error UI
20650
+ return jsxRuntime.jsxs("div", {
20651
+ className: "error-boundary",
20652
+ role: "alert",
20653
+ style: {
20654
+ padding: '2rem',
20655
+ textAlign: 'center',
20656
+ backgroundColor: '#fef2f2',
20657
+ border: '1px solid #fecaca',
20658
+ borderRadius: '8px',
20659
+ margin: '1rem',
20660
+ color: '#991b1b'
20661
+ },
20662
+ children: [jsxRuntime.jsxs("div", {
20663
+ style: {
20664
+ marginBottom: '1.5rem'
20665
+ },
20666
+ children: [jsxRuntime.jsx("h2", {
20667
+ style: {
20668
+ fontSize: '1.5rem',
20669
+ fontWeight: 'bold',
20670
+ marginBottom: '0.5rem',
20671
+ color: '#991b1b'
20672
+ },
20673
+ children: "Something went wrong"
20674
+ }), jsxRuntime.jsx("p", {
20675
+ style: {
20676
+ color: '#7f1d1d',
20677
+ marginBottom: '1rem'
20678
+ },
20679
+ children: "An unexpected error occurred in the application. Please try again or refresh the page."
20680
+ })]
20681
+ }), jsxRuntime.jsxs("div", {
20682
+ style: {
20683
+ display: 'flex',
20684
+ gap: '0.75rem',
20685
+ justifyContent: 'center',
20686
+ marginBottom: '1rem'
20687
+ },
20688
+ children: [jsxRuntime.jsx(Button, {
20689
+ variant: "contained",
20690
+ onClick: this.handleRetry,
20691
+ style: {
20692
+ backgroundColor: '#dc2626',
20693
+ color: 'white'
20694
+ },
20695
+ children: "Try Again"
20696
+ }), jsxRuntime.jsx(Button, {
20697
+ variant: "outlined",
20698
+ onClick: this.handleRefresh,
20699
+ style: {
20700
+ borderColor: '#dc2626',
20701
+ color: '#dc2626'
20702
+ },
20703
+ children: "Refresh Page"
20704
+ })]
20705
+ }), this.state.error && jsxRuntime.jsxs("details", {
20706
+ style: {
20707
+ textAlign: 'left',
20708
+ marginTop: '1rem',
20709
+ padding: '1rem',
20710
+ backgroundColor: '#f9fafb',
20711
+ border: '1px solid #d1d5db',
20712
+ borderRadius: '6px'
20713
+ },
20714
+ children: [jsxRuntime.jsx("summary", {
20715
+ style: {
20716
+ cursor: 'pointer',
20717
+ fontWeight: 'bold',
20718
+ marginBottom: '0.5rem',
20719
+ color: '#374151'
20720
+ },
20721
+ children: "Error Details (Development Mode)"
20722
+ }), jsxRuntime.jsxs("pre", {
20723
+ style: {
20724
+ fontSize: '0.75rem',
20725
+ color: '#374151',
20726
+ whiteSpace: 'pre-wrap',
20727
+ overflow: 'auto'
20728
+ },
20729
+ children: [this.state.error.toString(), this.state.errorInfo?.componentStack && jsxRuntime.jsxs(jsxRuntime.Fragment, {
20730
+ children: [jsxRuntime.jsx("br", {}), jsxRuntime.jsx("br", {}), "Component Stack:", this.state.errorInfo.componentStack]
20731
+ })]
20732
+ })]
20733
+ })]
20734
+ });
20735
+ }
20736
+ return this.props.children;
20737
+ }
20738
+ }
20739
+ /**
20740
+ * Higher-order component that wraps a component with ErrorBoundary
20741
+ */
20742
+ function withErrorBoundary(WrappedComponent, errorBoundaryProps) {
20743
+ const WithErrorBoundaryComponent = props => jsxRuntime.jsx(ErrorBoundary, {
20744
+ ...errorBoundaryProps,
20745
+ children: jsxRuntime.jsx(WrappedComponent, {
20746
+ ...props
20747
+ })
20748
+ });
20749
+ WithErrorBoundaryComponent.displayName = `withErrorBoundary(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
20750
+ return WithErrorBoundaryComponent;
20751
+ }
20752
+
20753
+ const AccessibilityContext = /*#__PURE__*/React.createContext(null);
20754
+ // Reducer
20755
+ const accessibilityReducer = (state, action) => {
20756
+ switch (action.type) {
20757
+ case 'SET_HIGH_CONTRAST':
20758
+ return {
20759
+ ...state,
20760
+ highContrast: action.payload
20761
+ };
20762
+ case 'SET_REDUCED_MOTION':
20763
+ return {
20764
+ ...state,
20765
+ reducedMotion: action.payload
20766
+ };
20767
+ case 'SET_LARGE_TEXT':
20768
+ return {
20769
+ ...state,
20770
+ largeText: action.payload
20771
+ };
20772
+ case 'SET_FOCUS_VISIBLE':
20773
+ return {
20774
+ ...state,
20775
+ focusVisible: action.payload
20776
+ };
20777
+ case 'SET_KEYBOARD_USER':
20778
+ return {
20779
+ ...state,
20780
+ isKeyboardUser: action.payload
20781
+ };
20782
+ case 'ADD_ISSUE':
20783
+ return {
20784
+ ...state,
20785
+ issues: [...state.issues, action.payload]
20786
+ };
20787
+ case 'CLEAR_ISSUES':
20788
+ return {
20789
+ ...state,
20790
+ issues: []
20791
+ };
20792
+ case 'SET_ANNOUNCEMENT':
20793
+ return {
20794
+ ...state,
20795
+ lastAnnouncement: action.payload
20796
+ };
20797
+ default:
20798
+ return state;
20799
+ }
20800
+ };
20801
+ // Initial state
20802
+ const initialState = {
20803
+ highContrast: false,
20804
+ reducedMotion: false,
20805
+ largeText: false,
20806
+ focusVisible: true,
20807
+ isKeyboardUser: false,
20808
+ issues: [],
20809
+ lastAnnouncement: null,
20810
+ preferences: {}
20811
+ };
20812
+ // ARIA Live Manager
20813
+ class AriaLiveManager {
20814
+ constructor() {
20815
+ this.politeRegion = null;
20816
+ this.assertiveRegion = null;
20817
+ this.createLiveRegions();
20818
+ }
20819
+ createLiveRegions() {
20820
+ if (typeof document === 'undefined') return;
20821
+ // Polite announcements
20822
+ this.politeRegion = document.createElement('div');
20823
+ this.politeRegion.setAttribute('aria-live', 'polite');
20824
+ this.politeRegion.setAttribute('aria-atomic', 'true');
20825
+ this.politeRegion.setAttribute('id', 'qwickapps-aria-live-polite');
20826
+ this.politeRegion.style.cssText = `
20827
+ position: absolute !important;
20828
+ left: -10000px !important;
20829
+ width: 1px !important;
20830
+ height: 1px !important;
20831
+ overflow: hidden !important;
20832
+ `;
20833
+ document.body.appendChild(this.politeRegion);
20834
+ // Assertive announcements
20835
+ this.assertiveRegion = document.createElement('div');
20836
+ this.assertiveRegion.setAttribute('aria-live', 'assertive');
20837
+ this.assertiveRegion.setAttribute('aria-atomic', 'true');
20838
+ this.assertiveRegion.setAttribute('id', 'qwickapps-aria-live-assertive');
20839
+ this.assertiveRegion.style.cssText = `
20840
+ position: absolute !important;
20841
+ left: -10000px !important;
20842
+ width: 1px !important;
20843
+ height: 1px !important;
20844
+ overflow: hidden !important;
20845
+ `;
20846
+ document.body.appendChild(this.assertiveRegion);
20847
+ }
20848
+ announce(message, level = 'polite') {
20849
+ if (level === 'assertive') {
20850
+ this.announceAssertive(message);
20851
+ } else {
20852
+ this.announcePolite(message);
20853
+ }
20854
+ }
20855
+ announcePolite(message) {
20856
+ if (!this.politeRegion) return;
20857
+ this.politeRegion.textContent = '';
20858
+ // Small delay ensures screen readers detect the change
20859
+ setTimeout(() => {
20860
+ if (this.politeRegion) {
20861
+ this.politeRegion.textContent = message;
20862
+ }
20863
+ }, 100);
20864
+ }
20865
+ announceAssertive(message) {
20866
+ if (!this.assertiveRegion) return;
20867
+ this.assertiveRegion.textContent = '';
20868
+ // Small delay ensures screen readers detect the change
20869
+ setTimeout(() => {
20870
+ if (this.assertiveRegion) {
20871
+ this.assertiveRegion.textContent = message;
20872
+ }
20873
+ }, 100);
20874
+ }
20875
+ }
20876
+ const ariaLiveManager = new AriaLiveManager();
20877
+ /**
20878
+ * Accessibility Provider Component
20879
+ * Provides comprehensive accessibility context and utilities
20880
+ *
20881
+ * Features:
20882
+ * - System preference detection (high contrast, reduced motion)
20883
+ * - Keyboard navigation detection
20884
+ * - ARIA live announcements
20885
+ * - Focus management
20886
+ * - Accessibility auditing
20887
+ * - Settings persistence
20888
+ */
20889
+ const AccessibilityProvider = ({
20890
+ children,
20891
+ enableAudit = "development" === 'development'
20892
+ }) => {
20893
+ const [state, dispatch] = React.useReducer(accessibilityReducer, initialState);
20894
+ React.useEffect(() => {
20895
+ // Detect user preferences from system
20896
+ detectUserPreferences();
20897
+ // Set up keyboard detection
20898
+ const keyboardCleanup = setupKeyboardDetection();
20899
+ // Initialize focus management
20900
+ initializeFocusManagement();
20901
+ // Run initial accessibility audit
20902
+ if (enableAudit) {
20903
+ runAccessibilityAudit();
20904
+ }
20905
+ // Cleanup
20906
+ return () => {
20907
+ if (keyboardCleanup) keyboardCleanup();
20908
+ };
20909
+ }, [enableAudit]);
20910
+ const detectUserPreferences = () => {
20911
+ if (typeof window === 'undefined') return;
20912
+ // High contrast mode
20913
+ if (window.matchMedia && window.matchMedia('(prefers-contrast: high)').matches) {
20914
+ dispatch({
20915
+ type: 'SET_HIGH_CONTRAST',
20916
+ payload: true
20917
+ });
20918
+ }
20919
+ // Reduced motion
20920
+ if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
20921
+ dispatch({
20922
+ type: 'SET_REDUCED_MOTION',
20923
+ payload: true
20924
+ });
20925
+ }
20926
+ // Listen for changes
20927
+ if (window.matchMedia) {
20928
+ const contrastMedia = window.matchMedia('(prefers-contrast: high)');
20929
+ const motionMedia = window.matchMedia('(prefers-reduced-motion: reduce)');
20930
+ const contrastHandler = e => {
20931
+ dispatch({
20932
+ type: 'SET_HIGH_CONTRAST',
20933
+ payload: e.matches
20934
+ });
20935
+ };
20936
+ const motionHandler = e => {
20937
+ dispatch({
20938
+ type: 'SET_REDUCED_MOTION',
20939
+ payload: e.matches
20940
+ });
20941
+ };
20942
+ contrastMedia.addEventListener('change', contrastHandler);
20943
+ motionMedia.addEventListener('change', motionHandler);
20944
+ // Return cleanup function
20945
+ return () => {
20946
+ contrastMedia.removeEventListener('change', contrastHandler);
20947
+ motionMedia.removeEventListener('change', motionHandler);
20948
+ };
20949
+ }
20950
+ };
20951
+ const setupKeyboardDetection = () => {
20952
+ if (typeof document === 'undefined') return;
20953
+ let keyboardUser = false;
20954
+ const handleKeyDown = e => {
20955
+ if (e.key === 'Tab') {
20956
+ keyboardUser = true;
20957
+ dispatch({
20958
+ type: 'SET_KEYBOARD_USER',
20959
+ payload: true
20960
+ });
20961
+ document.body.classList.add('keyboard-user');
20962
+ }
20963
+ };
20964
+ const handleMouseDown = () => {
20965
+ if (keyboardUser) {
20966
+ keyboardUser = false;
20967
+ dispatch({
20968
+ type: 'SET_KEYBOARD_USER',
20969
+ payload: false
20970
+ });
20971
+ document.body.classList.remove('keyboard-user');
20972
+ }
20973
+ };
20974
+ document.addEventListener('keydown', handleKeyDown);
20975
+ document.addEventListener('mousedown', handleMouseDown);
20976
+ return () => {
20977
+ document.removeEventListener('keydown', handleKeyDown);
20978
+ document.removeEventListener('mousedown', handleMouseDown);
20979
+ };
20980
+ };
20981
+ const initializeFocusManagement = () => {
20982
+ if (typeof document === 'undefined') return;
20983
+ // Enhanced focus indicators for keyboard users
20984
+ const style = document.createElement('style');
20985
+ style.textContent = `
20986
+ .keyboard-user *:focus {
20987
+ outline: 3px solid #005cee !important;
20988
+ outline-offset: 2px !important;
20989
+ }
20990
+
20991
+ .high-contrast *:focus {
20992
+ outline: 3px solid #ffffff !important;
20993
+ outline-offset: 2px !important;
20994
+ box-shadow: 0 0 0 1px #000000 !important;
20995
+ }
20996
+
20997
+ .reduced-motion * {
20998
+ animation-duration: 0.01ms !important;
20999
+ animation-iteration-count: 1 !important;
21000
+ transition-duration: 0.01ms !important;
21001
+ }
21002
+
21003
+ .large-text {
21004
+ font-size: 1.2em !important;
21005
+ }
21006
+ `;
21007
+ document.head.appendChild(style);
21008
+ };
21009
+ const runAccessibilityAudit = () => {
21010
+ if (typeof document === 'undefined') return;
21011
+ setTimeout(() => {
21012
+ const issues = [];
21013
+ // Check for images without alt text
21014
+ const images = document.querySelectorAll('img:not([alt])');
21015
+ images.forEach(img => {
21016
+ issues.push({
21017
+ type: 'missing-alt-text',
21018
+ message: 'Image missing alt attribute',
21019
+ level: 'error',
21020
+ element: img
21021
+ });
21022
+ });
21023
+ // Check for buttons without accessible names
21024
+ const buttons = document.querySelectorAll('button:not([aria-label]):not([title])');
21025
+ buttons.forEach(button => {
21026
+ if (!button.textContent?.trim()) {
21027
+ issues.push({
21028
+ type: 'unnamed-button',
21029
+ message: 'Button missing accessible name',
21030
+ level: 'error',
21031
+ element: button
21032
+ });
21033
+ }
21034
+ });
21035
+ // Check for form inputs without labels
21036
+ const inputs = document.querySelectorAll('input:not([aria-label]):not([title])');
21037
+ inputs.forEach(input => {
21038
+ const id = input.getAttribute('id');
21039
+ if (id) {
21040
+ const label = document.querySelector(`label[for="${id}"]`);
21041
+ if (!label) {
21042
+ issues.push({
21043
+ type: 'unlabeled-input',
21044
+ message: 'Form input missing label',
21045
+ level: 'error',
21046
+ element: input
21047
+ });
21048
+ }
21049
+ } else {
21050
+ issues.push({
21051
+ type: 'unlabeled-input',
21052
+ message: 'Form input missing label',
21053
+ level: 'error',
21054
+ element: input
21055
+ });
21056
+ }
21057
+ });
21058
+ dispatch({
21059
+ type: 'CLEAR_ISSUES'
21060
+ });
21061
+ issues.forEach(issue => {
21062
+ dispatch({
21063
+ type: 'ADD_ISSUE',
21064
+ payload: issue
21065
+ });
21066
+ });
21067
+ if (issues.length > 0) {
21068
+ console.group('🔍 Accessibility Issues Found');
21069
+ issues.forEach(issue => {
21070
+ const logMethod = issue.level === 'error' ? console.error : console.warn;
21071
+ logMethod(`${issue.type}: ${issue.message}`);
21072
+ });
21073
+ console.groupEnd();
21074
+ }
21075
+ }, 1000);
21076
+ };
21077
+ // Context value
21078
+ const contextValue = {
21079
+ ...state,
21080
+ // Actions
21081
+ setHighContrast: enabled => dispatch({
21082
+ type: 'SET_HIGH_CONTRAST',
21083
+ payload: enabled
21084
+ }),
21085
+ setReducedMotion: enabled => dispatch({
21086
+ type: 'SET_REDUCED_MOTION',
21087
+ payload: enabled
21088
+ }),
21089
+ setLargeText: enabled => dispatch({
21090
+ type: 'SET_LARGE_TEXT',
21091
+ payload: enabled
21092
+ }),
21093
+ setFocusVisible: enabled => dispatch({
21094
+ type: 'SET_FOCUS_VISIBLE',
21095
+ payload: enabled
21096
+ }),
21097
+ // Utilities
21098
+ announce: (message, level = 'polite') => {
21099
+ ariaLiveManager.announce(message, level);
21100
+ dispatch({
21101
+ type: 'SET_ANNOUNCEMENT',
21102
+ payload: {
21103
+ message,
21104
+ level,
21105
+ timestamp: Date.now()
21106
+ }
21107
+ });
21108
+ },
21109
+ announcePolite: message => {
21110
+ ariaLiveManager.announcePolite(message);
21111
+ dispatch({
21112
+ type: 'SET_ANNOUNCEMENT',
21113
+ payload: {
21114
+ message,
21115
+ level: 'polite',
21116
+ timestamp: Date.now()
21117
+ }
21118
+ });
21119
+ },
21120
+ announceAssertive: message => {
21121
+ ariaLiveManager.announceAssertive(message);
21122
+ dispatch({
21123
+ type: 'SET_ANNOUNCEMENT',
21124
+ payload: {
21125
+ message,
21126
+ level: 'assertive',
21127
+ timestamp: Date.now()
21128
+ }
21129
+ });
21130
+ },
21131
+ addIssue: issue => dispatch({
21132
+ type: 'ADD_ISSUE',
21133
+ payload: issue
21134
+ }),
21135
+ clearIssues: () => dispatch({
21136
+ type: 'CLEAR_ISSUES'
21137
+ }),
21138
+ // Audit function
21139
+ runAudit: runAccessibilityAudit
21140
+ };
21141
+ // Apply CSS classes based on preferences
21142
+ React.useEffect(() => {
21143
+ if (typeof document === 'undefined') return;
21144
+ const {
21145
+ highContrast,
21146
+ reducedMotion,
21147
+ largeText
21148
+ } = state;
21149
+ document.body.classList.toggle('high-contrast', highContrast);
21150
+ document.body.classList.toggle('reduced-motion', reducedMotion);
21151
+ document.body.classList.toggle('large-text', largeText);
21152
+ }, [state.highContrast, state.reducedMotion, state.largeText]);
21153
+ return jsxRuntime.jsx(AccessibilityContext.Provider, {
21154
+ value: contextValue,
21155
+ children: children
21156
+ });
21157
+ };
21158
+ /**
21159
+ * Hook to access accessibility context
21160
+ */
21161
+ const useAccessibility = () => {
21162
+ const context = React.useContext(AccessibilityContext);
21163
+ if (!context) {
21164
+ throw new Error('useAccessibility must be used within an AccessibilityProvider');
21165
+ }
21166
+ return context;
21167
+ };
21168
+ /**
21169
+ * Higher-Order Component for accessibility enhancements
21170
+ */
21171
+ const withAccessibility = WrappedComponent => {
21172
+ const AccessibilityEnhancedComponent = props => {
21173
+ const accessibility = useAccessibility();
21174
+ return jsxRuntime.jsx(WrappedComponent, {
21175
+ ...props,
21176
+ accessibility: accessibility
21177
+ });
21178
+ };
21179
+ AccessibilityEnhancedComponent.displayName = `withAccessibility(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
21180
+ return AccessibilityEnhancedComponent;
21181
+ };
21182
+
20584
21183
  const QwickApp = ({
20585
21184
  children,
20586
21185
  className,
@@ -20642,16 +21241,20 @@ const QwickApp = ({
20642
21241
  dataSource: dataSource,
20643
21242
  children: content
20644
21243
  }) : content;
20645
- const appContent = jsxRuntime.jsx("div", {
20646
- className: `qwick-app ${className || ''}`,
20647
- style: style,
20648
- children: jsxRuntime.jsx(ThemeProvider, {
20649
- appId: appId,
20650
- defaultTheme: defaultTheme,
20651
- defaultPalette: defaultPalette,
20652
- children: jsxRuntime.jsx(QwickAppContext.Provider, {
20653
- value: contextValue,
20654
- children: wrappedContent
21244
+ const appContent = jsxRuntime.jsx(ErrorBoundary, {
21245
+ children: jsxRuntime.jsx(AccessibilityProvider, {
21246
+ children: jsxRuntime.jsx("div", {
21247
+ className: `qwick-app ${className || ''}`,
21248
+ style: style,
21249
+ children: jsxRuntime.jsx(ThemeProvider, {
21250
+ appId: appId,
21251
+ defaultTheme: defaultTheme,
21252
+ defaultPalette: defaultPalette,
21253
+ children: jsxRuntime.jsx(QwickAppContext.Provider, {
21254
+ value: contextValue,
21255
+ children: wrappedContent
21256
+ })
21257
+ })
20655
21258
  })
20656
21259
  })
20657
21260
  });
@@ -20759,6 +21362,186 @@ const setCSSVariable = (property, value) => {
20759
21362
  document.documentElement.style.setProperty(property, value);
20760
21363
  };
20761
21364
 
21365
+ /**
21366
+ * Generic Breadcrumbs component for navigation hierarchy
21367
+ *
21368
+ * Features:
21369
+ * - Accessible navigation with proper ARIA labels
21370
+ * - Customizable separators and icons
21371
+ * - Responsive design with item truncation
21372
+ * - Support for custom navigation handlers
21373
+ * - Keyboard navigation support
21374
+ * - Screen reader friendly
21375
+ */
21376
+ const Breadcrumbs = ({
21377
+ items,
21378
+ separator = '/',
21379
+ className = '',
21380
+ onNavigate,
21381
+ maxItems,
21382
+ showRoot = true
21383
+ }) => {
21384
+ // Filter and prepare items
21385
+ let displayItems = showRoot ? items : items.slice(1);
21386
+ // Handle max items with ellipsis
21387
+ if (maxItems && displayItems.length > maxItems) {
21388
+ const firstItems = displayItems.slice(0, 1);
21389
+ const lastItems = displayItems.slice(-Math.max(1, maxItems - 2));
21390
+ displayItems = [...firstItems, {
21391
+ label: '...',
21392
+ href: undefined,
21393
+ current: false
21394
+ }, ...lastItems];
21395
+ }
21396
+ const handleItemClick = (e, item, index) => {
21397
+ if (onNavigate) {
21398
+ e.preventDefault();
21399
+ onNavigate(item, index);
21400
+ }
21401
+ };
21402
+ const handleKeyDown = (e, item, index) => {
21403
+ if (e.key === 'Enter' || e.key === ' ') {
21404
+ e.preventDefault();
21405
+ if (onNavigate) {
21406
+ onNavigate(item, index);
21407
+ } else if (item.href) {
21408
+ window.location.href = item.href;
21409
+ }
21410
+ }
21411
+ };
21412
+ if (displayItems.length <= 1) {
21413
+ return null;
21414
+ }
21415
+ return jsxRuntime.jsx("nav", {
21416
+ className: `breadcrumbs ${className}`,
21417
+ role: "navigation",
21418
+ "aria-label": "Breadcrumb navigation",
21419
+ style: {
21420
+ display: 'flex',
21421
+ alignItems: 'center',
21422
+ fontSize: '14px',
21423
+ color: '#6b7280',
21424
+ ...defaultStyles.nav
21425
+ },
21426
+ children: jsxRuntime.jsx("ol", {
21427
+ style: {
21428
+ display: 'flex',
21429
+ alignItems: 'center',
21430
+ listStyle: 'none',
21431
+ margin: 0,
21432
+ padding: 0,
21433
+ gap: '8px'
21434
+ },
21435
+ children: displayItems.map((item, index) => {
21436
+ const isLast = index === displayItems.length - 1;
21437
+ const isClickable = (item.href || onNavigate) && !item.current && item.label !== '...';
21438
+ return jsxRuntime.jsxs("li", {
21439
+ style: {
21440
+ display: 'flex',
21441
+ alignItems: 'center'
21442
+ },
21443
+ children: [isClickable ? jsxRuntime.jsxs("a", {
21444
+ href: item.href,
21445
+ onClick: e => handleItemClick(e, item, index),
21446
+ onKeyDown: e => handleKeyDown(e, item, index),
21447
+ style: {
21448
+ ...defaultStyles.link,
21449
+ color: isLast ? '#374151' : '#6b7280',
21450
+ textDecoration: 'none',
21451
+ display: 'flex',
21452
+ alignItems: 'center',
21453
+ gap: '4px'
21454
+ },
21455
+ tabIndex: 0,
21456
+ "aria-current": item.current ? 'page' : undefined,
21457
+ children: [item.icon && jsxRuntime.jsx("span", {
21458
+ style: {
21459
+ display: 'flex',
21460
+ alignItems: 'center'
21461
+ },
21462
+ "aria-hidden": "true",
21463
+ children: item.icon
21464
+ }), jsxRuntime.jsx("span", {
21465
+ children: item.label
21466
+ })]
21467
+ }) : jsxRuntime.jsxs("span", {
21468
+ style: {
21469
+ ...defaultStyles.current,
21470
+ color: isLast ? '#111827' : '#6b7280',
21471
+ fontWeight: isLast ? 600 : 400,
21472
+ display: 'flex',
21473
+ alignItems: 'center',
21474
+ gap: '4px'
21475
+ },
21476
+ "aria-current": item.current ? 'page' : undefined,
21477
+ children: [item.icon && jsxRuntime.jsx("span", {
21478
+ style: {
21479
+ display: 'flex',
21480
+ alignItems: 'center'
21481
+ },
21482
+ "aria-hidden": "true",
21483
+ children: item.icon
21484
+ }), jsxRuntime.jsx("span", {
21485
+ children: item.label
21486
+ })]
21487
+ }), !isLast && jsxRuntime.jsx("span", {
21488
+ style: {
21489
+ display: 'flex',
21490
+ alignItems: 'center',
21491
+ marginLeft: '8px',
21492
+ color: '#d1d5db',
21493
+ fontSize: '12px'
21494
+ },
21495
+ "aria-hidden": "true",
21496
+ children: separator
21497
+ })]
21498
+ }, `${item.label}-${index}`);
21499
+ })
21500
+ })
21501
+ });
21502
+ };
21503
+ // Default styles
21504
+ const defaultStyles = {
21505
+ nav: {
21506
+ padding: '8px 0'
21507
+ },
21508
+ link: {
21509
+ transition: 'color 0.2s ease',
21510
+ cursor: 'pointer',
21511
+ borderRadius: '4px',
21512
+ padding: '4px',
21513
+ margin: '-4px'
21514
+ },
21515
+ current: {
21516
+ padding: '4px'
21517
+ }
21518
+ };
21519
+ /**
21520
+ * Hook for managing breadcrumb state
21521
+ */
21522
+ const useBreadcrumbs = () => {
21523
+ const [breadcrumbs, setBreadcrumbs] = React.useState([]);
21524
+ const addBreadcrumb = React.useCallback(item => {
21525
+ setBreadcrumbs(prev => [...prev, item]);
21526
+ }, []);
21527
+ const removeBreadcrumb = React.useCallback(index => {
21528
+ setBreadcrumbs(prev => prev.filter((_, i) => i !== index));
21529
+ }, []);
21530
+ const setBreadcrumbsCurrent = React.useCallback(items => {
21531
+ setBreadcrumbs(items);
21532
+ }, []);
21533
+ const clearBreadcrumbs = React.useCallback(() => {
21534
+ setBreadcrumbs([]);
21535
+ }, []);
21536
+ return {
21537
+ breadcrumbs,
21538
+ addBreadcrumb,
21539
+ removeBreadcrumb,
21540
+ setBreadcrumbs: setBreadcrumbsCurrent,
21541
+ clearBreadcrumbs
21542
+ };
21543
+ };
21544
+
20762
21545
  const QwickAppsLogo = props => {
20763
21546
  const {
20764
21547
  styleProps,
@@ -24226,9 +25009,11 @@ exports.ActionType = void 0;
24226
25009
  })(exports.ActionType || (exports.ActionType = {}));
24227
25010
 
24228
25011
  exports.AVAILABLE_PALETTES = AVAILABLE_PALETTES;
25012
+ exports.AccessibilityProvider = AccessibilityProvider;
24229
25013
  exports.AllPalettes = AllPalettes;
24230
25014
  exports.Article = Article;
24231
25015
  exports.BasePage = BasePage;
25016
+ exports.Breadcrumbs = Breadcrumbs;
24232
25017
  exports.Button = Button;
24233
25018
  exports.CardListGrid = CardListGrid;
24234
25019
  exports.ChoiceInputField = ChoiceInputField;
@@ -24238,6 +25023,7 @@ exports.CoverImageHeader = CoverImageHeader;
24238
25023
  exports.DataProvider = DataProvider;
24239
25024
  exports.DataProxy = DataProxy;
24240
25025
  exports.DimensionsProvider = DimensionsProvider;
25026
+ exports.ErrorBoundary = ErrorBoundary;
24241
25027
  exports.FeatureCard = FeatureCard;
24242
25028
  exports.FeatureGrid = FeatureGrid;
24243
25029
  exports.Footer = Footer;
@@ -24309,7 +25095,9 @@ exports.setCSSVariable = setCSSVariable;
24309
25095
  exports.setPalette = setPalette;
24310
25096
  exports.setTheme = setTheme;
24311
25097
  exports.t = t;
25098
+ exports.useAccessibility = useAccessibility;
24312
25099
  exports.useBaseProps = useBaseProps;
25100
+ exports.useBreadcrumbs = useBreadcrumbs;
24313
25101
  exports.useData = useData;
24314
25102
  exports.useDataBinding = useDataBinding;
24315
25103
  exports.useDataContext = useDataContext;
@@ -24322,3 +25110,5 @@ exports.useSafeLocation = useSafeLocation;
24322
25110
  exports.useSafeNavigate = useSafeNavigate;
24323
25111
  exports.useTemplate = useTemplate;
24324
25112
  exports.useTheme = useTheme;
25113
+ exports.withAccessibility = withAccessibility;
25114
+ exports.withErrorBoundary = withErrorBoundary;