mtrl 0.0.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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/index.js +10 -0
  4. package/package.json +17 -0
  5. package/src/components/button/api.js +54 -0
  6. package/src/components/button/button.js +81 -0
  7. package/src/components/button/config.js +8 -0
  8. package/src/components/button/constants.js +63 -0
  9. package/src/components/button/index.js +2 -0
  10. package/src/components/button/styles.scss +231 -0
  11. package/src/components/checkbox/api.js +45 -0
  12. package/src/components/checkbox/checkbox.js +95 -0
  13. package/src/components/checkbox/constants.js +88 -0
  14. package/src/components/checkbox/index.js +2 -0
  15. package/src/components/checkbox/styles.scss +183 -0
  16. package/src/components/container/api.js +42 -0
  17. package/src/components/container/container.js +45 -0
  18. package/src/components/container/index.js +2 -0
  19. package/src/components/container/styles.scss +59 -0
  20. package/src/components/list/constants.js +89 -0
  21. package/src/components/list/index.js +2 -0
  22. package/src/components/list/list-item.js +147 -0
  23. package/src/components/list/list.js +267 -0
  24. package/src/components/list/styles/_list-item.scss +142 -0
  25. package/src/components/list/styles/_list.scss +89 -0
  26. package/src/components/list/styles/_variables.scss +13 -0
  27. package/src/components/list/styles.scss +19 -0
  28. package/src/components/navigation/api.js +43 -0
  29. package/src/components/navigation/constants.js +235 -0
  30. package/src/components/navigation/features/items.js +192 -0
  31. package/src/components/navigation/index.js +2 -0
  32. package/src/components/navigation/nav-item.js +137 -0
  33. package/src/components/navigation/navigation.js +55 -0
  34. package/src/components/navigation/styles/_bar.scss +51 -0
  35. package/src/components/navigation/styles/_base.scss +129 -0
  36. package/src/components/navigation/styles/_drawer.scss +169 -0
  37. package/src/components/navigation/styles/_rail.scss +65 -0
  38. package/src/components/navigation/styles.scss +6 -0
  39. package/src/components/snackbar/api.js +125 -0
  40. package/src/components/snackbar/constants.js +41 -0
  41. package/src/components/snackbar/features.js +69 -0
  42. package/src/components/snackbar/index.js +2 -0
  43. package/src/components/snackbar/position.js +63 -0
  44. package/src/components/snackbar/queue.js +74 -0
  45. package/src/components/snackbar/snackbar.js +70 -0
  46. package/src/components/snackbar/styles.scss +182 -0
  47. package/src/components/switch/api.js +44 -0
  48. package/src/components/switch/constants.js +80 -0
  49. package/src/components/switch/index.js +2 -0
  50. package/src/components/switch/styles.scss +172 -0
  51. package/src/components/switch/switch.js +71 -0
  52. package/src/components/textfield/api.js +49 -0
  53. package/src/components/textfield/constants.js +81 -0
  54. package/src/components/textfield/index.js +2 -0
  55. package/src/components/textfield/styles/base.scss +107 -0
  56. package/src/components/textfield/styles/filled.scss +58 -0
  57. package/src/components/textfield/styles/outlined.scss +66 -0
  58. package/src/components/textfield/styles.scss +6 -0
  59. package/src/components/textfield/textfield.js +68 -0
  60. package/src/core/build/constants.js +51 -0
  61. package/src/core/build/icon.js +78 -0
  62. package/src/core/build/ripple.js +92 -0
  63. package/src/core/build/text.js +54 -0
  64. package/src/core/collection/adapters/base.js +26 -0
  65. package/src/core/collection/adapters/mongodb.js +232 -0
  66. package/src/core/collection/adapters/route.js +201 -0
  67. package/src/core/collection/collection.js +259 -0
  68. package/src/core/collection/list-manager.js +157 -0
  69. package/src/core/compose/base.js +8 -0
  70. package/src/core/compose/component.js +225 -0
  71. package/src/core/compose/features/checkable.js +114 -0
  72. package/src/core/compose/features/disabled.js +25 -0
  73. package/src/core/compose/features/events.js +48 -0
  74. package/src/core/compose/features/icon.js +33 -0
  75. package/src/core/compose/features/index.js +20 -0
  76. package/src/core/compose/features/input.js +92 -0
  77. package/src/core/compose/features/lifecycle.js +69 -0
  78. package/src/core/compose/features/position.js +60 -0
  79. package/src/core/compose/features/ripple.js +32 -0
  80. package/src/core/compose/features/size.js +9 -0
  81. package/src/core/compose/features/style.js +12 -0
  82. package/src/core/compose/features/text.js +17 -0
  83. package/src/core/compose/features/textinput.js +118 -0
  84. package/src/core/compose/features/textlabel.js +28 -0
  85. package/src/core/compose/features/track.js +49 -0
  86. package/src/core/compose/features/variant.js +9 -0
  87. package/src/core/compose/features/withEvents.js +67 -0
  88. package/src/core/compose/index.js +16 -0
  89. package/src/core/compose/pipe.js +69 -0
  90. package/src/core/config.js +140 -0
  91. package/src/core/dom/attributes.js +33 -0
  92. package/src/core/dom/classes.js +70 -0
  93. package/src/core/dom/create.js +133 -0
  94. package/src/core/dom/events.js +175 -0
  95. package/src/core/dom/index.js +5 -0
  96. package/src/core/dom/utils.js +22 -0
  97. package/src/core/index.js +23 -0
  98. package/src/core/layout/index.js +93 -0
  99. package/src/core/state/disabled.js +14 -0
  100. package/src/core/state/emitter.js +63 -0
  101. package/src/core/state/events.js +29 -0
  102. package/src/core/state/index.js +6 -0
  103. package/src/core/state/lifecycle.js +64 -0
  104. package/src/core/state/store.js +112 -0
  105. package/src/core/utils/index.js +39 -0
  106. package/src/core/utils/mobile.js +74 -0
  107. package/src/core/utils/object.js +22 -0
  108. package/src/core/utils/validate.js +37 -0
  109. package/src/index.js +11 -0
  110. package/src/styles/abstract/_base.scss +2 -0
  111. package/src/styles/abstract/_config.scss +28 -0
  112. package/src/styles/abstract/_functions.scss +124 -0
  113. package/src/styles/abstract/_mixins.scss +261 -0
  114. package/src/styles/abstract/_variables.scss +158 -0
  115. package/src/styles/main.scss +78 -0
  116. package/src/styles/themes/_base-theme.scss +49 -0
  117. package/src/styles/themes/_baseline.scss +90 -0
  118. package/src/styles/themes/_forest.scss +71 -0
  119. package/src/styles/themes/_index.scss +6 -0
  120. package/src/styles/themes/_ocean.scss +71 -0
  121. package/src/styles/themes/_sunset.scss +55 -0
@@ -0,0 +1,169 @@
1
+ // src/components/navigation/styles/_drawer.scss
2
+ @use 'sass:map';
3
+ @use '../../../styles/abstract/config' as c;
4
+ @use 'base';
5
+
6
+ .#{c.$prefix}-nav {
7
+ &--drawer,
8
+ &--drawer-modal,
9
+ &--drawer-standard {
10
+ flex-direction: column;
11
+ width: 256px;
12
+ height: 100%;
13
+ padding: 12px 0;
14
+ transition: transform 0.2s ease-in-out;
15
+ transform: translateX(0);
16
+
17
+ // Hidden state
18
+ &.#{c.$prefix}-nav--hidden {
19
+ transform: translateX(-100%);
20
+
21
+ @include c.rtl {
22
+ transform: translateX(100%);
23
+ }
24
+ }
25
+
26
+ // Item container for nesting
27
+ .#{c.$prefix}-nav-item-container {
28
+ width: 100%;
29
+ display: flex;
30
+ flex-direction: column;
31
+ }
32
+
33
+ // Base nav item styles
34
+ .#{c.$prefix}-nav-item {
35
+ padding: 12px 16px;
36
+ align-items: center;
37
+ justify-content: flex-start;
38
+ border-radius: 28px;
39
+ margin: 0 12px;
40
+ width: calc(100% - 24px);
41
+
42
+ &:hover {
43
+ @include c.state-layer(var(--mtrl-sys-color-on-surface), 'hover');
44
+ }
45
+
46
+ &--active {
47
+ background-color: var(--mtrl-sys-color-secondary-container);
48
+ color: var(--mtrl-sys-color-on-secondary-container);
49
+
50
+ &:hover {
51
+ background-color: var(--mtrl-sys-color-secondary-container);
52
+ }
53
+ }
54
+
55
+ &-icon {
56
+ margin-right: 12px;
57
+ flex-shrink: 0;
58
+ }
59
+
60
+ &-label {
61
+ @include c.typography('label-large');
62
+ flex-grow: 1;
63
+ text-align: left;
64
+ }
65
+
66
+ &-badge {
67
+ position: static;
68
+ margin-left: auto;
69
+ margin-right: 8px;
70
+ }
71
+ }
72
+
73
+ // Nested navigation styles
74
+ .#{c.$prefix}-nav-nested-container {
75
+ display: flex;
76
+ flex-direction: column;
77
+ width: 100%;
78
+ margin-left: 28px;
79
+ padding-right: 12px;
80
+
81
+ &[hidden] {
82
+ display: none;
83
+ }
84
+
85
+ // Adjust nested items styles
86
+ .#{c.$prefix}-nav-item {
87
+ margin: 0;
88
+ padding: 8px 16px;
89
+ font-size: 14px;
90
+
91
+ &-icon {
92
+ width: 20px;
93
+ height: 20px;
94
+ padding: 6px;
95
+
96
+ svg {
97
+ width: 20px;
98
+ height: 20px;
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ // Expand icon styles
105
+ .#{c.$prefix}-nav-expand-icon {
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ width: 20px;
110
+ height: 20px;
111
+ margin-left: auto;
112
+ color: inherit;
113
+ @include c.motion-transition(transform);
114
+
115
+ svg {
116
+ width: 20px;
117
+ height: 20px;
118
+ fill: none;
119
+ stroke: currentColor;
120
+ stroke-width: 2px;
121
+ }
122
+ }
123
+
124
+ // RTL support
125
+ @include c.rtl {
126
+ .#{c.$prefix}-nav-item {
127
+ &-icon {
128
+ margin-right: 0;
129
+ margin-left: 12px;
130
+ }
131
+
132
+ &-label {
133
+ text-align: right;
134
+ }
135
+
136
+ &-badge {
137
+ margin-left: 8px;
138
+ margin-right: auto;
139
+ }
140
+ }
141
+
142
+ .#{c.$prefix}-nav-nested-container {
143
+ margin-left: 0;
144
+ margin-right: 28px;
145
+ padding-right: 0;
146
+ padding-left: 12px;
147
+ }
148
+
149
+ .#{c.$prefix}-nav-expand-icon {
150
+ margin-left: 0;
151
+ margin-right: auto;
152
+ transform: scaleX(-1);
153
+ }
154
+ }
155
+ }
156
+
157
+ &--drawer-modal {
158
+ @include c.elevation(2);
159
+ }
160
+
161
+ &--drawer-standard {
162
+ border-right: 1px solid var(--mtrl-sys-color-outline-variant);
163
+
164
+ @include c.rtl {
165
+ border-right: none;
166
+ border-left: 1px solid var(--mtrl-sys-color-outline-variant);
167
+ }
168
+ }
169
+ }
@@ -0,0 +1,65 @@
1
+ // src/components/navigation/styles/_rail.scss
2
+ @use 'sass:map';
3
+ @use '../../../styles/abstract/config' as c;
4
+ @use 'base';
5
+
6
+ .#{c.$prefix}-nav {
7
+ &--rail {
8
+ flex-direction: column;
9
+ width: 80px;
10
+ height: 100%;
11
+ padding: 12px 0;
12
+
13
+ .#{c.$prefix}-nav-item {
14
+ flex-direction: column;
15
+ width: 100%;
16
+ min-height: 56px;
17
+ padding: 2px;
18
+ margin: -2px auto 14px;
19
+ gap: 0;
20
+
21
+ &:hover {
22
+ .#{c.$prefix}-nav-item-icon {
23
+ @include c.state-layer(var(--mtrl-sys-color-on-surface), 'hover');
24
+ }
25
+ }
26
+
27
+ &--active {
28
+ .#{c.$prefix}-nav-item-icon {
29
+ background-color: var(--mtrl-sys-color-secondary-container);
30
+ }
31
+ }
32
+
33
+ &-icon {
34
+ margin-bottom: 4px;
35
+ padding: 8px;
36
+ width: 56px;
37
+ height: 32px;
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ border-radius: 16px;
42
+ @include c.motion-transition(background-color);
43
+
44
+ svg {
45
+ width: 24px;
46
+ height: 24px;
47
+ fill: currentColor;
48
+ }
49
+ }
50
+
51
+ &-label {
52
+ @include c.typography('label-small');
53
+ text-align: center;
54
+ color: inherit;
55
+ font-size: 12px;
56
+ line-height: 16px;
57
+ }
58
+
59
+ &-badge {
60
+ top: 4px;
61
+ right: 16px;
62
+ }
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,6 @@
1
+ // src/components/navigation/styles.scss
2
+ @use '../../styles/abstract/config' as c;
3
+ @use 'styles/base';
4
+ @use 'styles/rail';
5
+ @use 'styles/drawer';
6
+ @use 'styles/bar';
@@ -0,0 +1,125 @@
1
+ // src/components/snackbar/api.js
2
+
3
+ /**
4
+ * Enhances snackbar component with API methods
5
+ * @param {Object} options - API configuration
6
+ * @param {Object} options.lifecycle - Lifecycle handlers
7
+ * @param {Object} options.queue - Snackbar queue manager
8
+ */
9
+ export const withAPI = ({ lifecycle, queue }) => (component) => {
10
+ if (!queue) {
11
+ throw new Error('Snackbar queue is required')
12
+ }
13
+
14
+ let isVisible = false
15
+
16
+ const enhancedComponent = {
17
+ ...component,
18
+ element: component.element,
19
+
20
+ /**
21
+ * Shows the snackbar with animation
22
+ */
23
+ show () {
24
+ if (isVisible) return this
25
+ isVisible = true
26
+
27
+ queue.add({
28
+ ...this,
29
+ _show: () => {
30
+ document.body.appendChild(component.element)
31
+
32
+ // Force reflow for animation
33
+ const _ = component.element.offsetHeight
34
+
35
+ component.element.classList.add(`${component.getClass('snackbar')}--visible`)
36
+
37
+ if (component.timer) {
38
+ component.timer.start()
39
+ }
40
+
41
+ return this
42
+ }
43
+ })
44
+
45
+ return this
46
+ },
47
+
48
+ /**
49
+ * Hides the snackbar with animation and cleanup
50
+ */
51
+ hide () {
52
+ if (!isVisible) return this
53
+ isVisible = false
54
+
55
+ if (component.timer) {
56
+ component.timer.stop()
57
+ }
58
+
59
+ const handleTransitionEnd = (event) => {
60
+ if (event.propertyName !== 'opacity') return
61
+
62
+ component.element.removeEventListener('transitionend', handleTransitionEnd)
63
+ if (component.element.parentNode) {
64
+ component.element.remove()
65
+ }
66
+ }
67
+
68
+ component.element.addEventListener('transitionend', handleTransitionEnd)
69
+ component.element.classList.remove(`${component.getClass('snackbar')}--visible`)
70
+
71
+ return this
72
+ },
73
+
74
+ setMessage (text) {
75
+ component.text.setText(text)
76
+ return this
77
+ },
78
+
79
+ getMessage () {
80
+ return component.text.getText()
81
+ },
82
+
83
+ on: component.on,
84
+ off: component.off,
85
+
86
+ destroy () {
87
+ if (isVisible) {
88
+ component.element.remove()
89
+ }
90
+ if (component.timer) {
91
+ component.timer.stop()
92
+ }
93
+ lifecycle.destroy()
94
+ }
95
+ }
96
+
97
+ // Set up action button handler
98
+ if (component.actionButton) {
99
+ component.actionButton.addEventListener('click', () => {
100
+ component.emit('action')
101
+ component.emit('dismiss') // Emit dismiss to handle queue cleanup
102
+ })
103
+ }
104
+
105
+ // Set up dismiss handler
106
+ if (component.on) {
107
+ // Store the handler reference so it can be properly removed
108
+ const dismissHandler = () => {
109
+ if (isVisible) {
110
+ enhancedComponent.hide()
111
+ }
112
+ }
113
+
114
+ component.on('dismiss', dismissHandler)
115
+
116
+ // Add cleanup to lifecycle
117
+ const originalDestroy = lifecycle.destroy
118
+ lifecycle.destroy = () => {
119
+ component.off('dismiss', dismissHandler)
120
+ originalDestroy?.call(lifecycle)
121
+ }
122
+ }
123
+
124
+ return enhancedComponent
125
+ }
@@ -0,0 +1,41 @@
1
+ // src/components/snackbar/constants.js
2
+
3
+ export const SNACKBAR_VARIANTS = {
4
+ BASIC: 'basic',
5
+ ACTION: 'action' // With action button
6
+ }
7
+
8
+ export const SNACKBAR_POSITIONS = {
9
+ CENTER: 'center',
10
+ START: 'start',
11
+ END: 'end'
12
+ }
13
+
14
+ export const SNACKBAR_SCHEMA = {
15
+ variant: {
16
+ type: 'string',
17
+ enum: Object.values(SNACKBAR_VARIANTS),
18
+ required: false
19
+ },
20
+ position: {
21
+ type: 'string',
22
+ enum: Object.values(SNACKBAR_POSITIONS),
23
+ required: false
24
+ },
25
+ message: {
26
+ type: 'string',
27
+ required: true
28
+ },
29
+ action: {
30
+ type: 'string',
31
+ required: false
32
+ },
33
+ duration: {
34
+ type: 'number',
35
+ required: false
36
+ },
37
+ class: {
38
+ type: 'string',
39
+ required: false
40
+ }
41
+ }
@@ -0,0 +1,69 @@
1
+ // src/components/snackbar/features.js
2
+
3
+ /**
4
+ * Adds action button to snackbar
5
+ */
6
+ export const withActionButton = (config) => (component) => {
7
+ if (!config.action) return component
8
+
9
+ const button = document.createElement('button')
10
+ button.className = `${config.prefix}-snackbar-action`
11
+ button.textContent = config.action
12
+
13
+ component.element.appendChild(button)
14
+
15
+ return {
16
+ ...component,
17
+ actionButton: button
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Adds auto-dismiss timer functionality
23
+ */
24
+ export const withDismissTimer = (config) => (component) => {
25
+ let timeoutId = null
26
+
27
+ const startTimer = () => {
28
+ // Clear any existing timer
29
+ if (timeoutId) {
30
+ clearTimeout(timeoutId)
31
+ timeoutId = null
32
+ }
33
+
34
+ // Only start timer if duration is positive and numeric
35
+ if (typeof config.duration === 'number' && config.duration > 0) {
36
+ timeoutId = setTimeout(() => {
37
+ if (component.element && component.emit) {
38
+ component.emit('dismiss')
39
+ }
40
+ }, config.duration)
41
+ }
42
+ }
43
+
44
+ const stopTimer = () => {
45
+ if (timeoutId) {
46
+ clearTimeout(timeoutId)
47
+ timeoutId = null
48
+ }
49
+ }
50
+
51
+ // Clean up on destroy
52
+ const originalDestroy = component.lifecycle?.destroy
53
+ if (component.lifecycle) {
54
+ component.lifecycle.destroy = () => {
55
+ stopTimer()
56
+ if (originalDestroy) {
57
+ originalDestroy.call(component.lifecycle)
58
+ }
59
+ }
60
+ }
61
+
62
+ return {
63
+ ...component,
64
+ timer: {
65
+ start: startTimer,
66
+ stop: stopTimer
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,2 @@
1
+ // src/components/snackbar/index.js
2
+ export { default } from './snackbar.js'
@@ -0,0 +1,63 @@
1
+ // src/components/snackbar/position.js
2
+
3
+ import { SNACKBAR_POSITIONS } from './constants'
4
+
5
+ /**
6
+ * Adds position handling to snackbar
7
+ * @param {Object} config - Position configuration
8
+ * @param {string} config.prefix - Component prefix
9
+ * @param {string} config.position - Position variant (start, center, end)
10
+ */
11
+ export const withPosition = (config) => (component) => {
12
+ const position = config.position || SNACKBAR_POSITIONS.CENTER
13
+ const positionClass = `${config.prefix}-snackbar--${position}`
14
+
15
+ // Add position class
16
+ component.element.classList.add(positionClass)
17
+
18
+ // Method to update position
19
+ const setPosition = (newPosition) => {
20
+ // Remove current position class
21
+ component.element.classList.remove(positionClass)
22
+
23
+ // Add new position class
24
+ const newPositionClass = `${config.prefix}-snackbar--${newPosition}`
25
+ component.element.classList.add(newPositionClass)
26
+
27
+ // Update visible state transform for center position
28
+ if (component.element.classList.contains(`${config.prefix}-snackbar--visible`)) {
29
+ if (newPosition === SNACKBAR_POSITIONS.CENTER) {
30
+ component.element.style.transform = 'translateX(-50%) scale(1)'
31
+ } else {
32
+ component.element.style.transform = 'scale(1)'
33
+ }
34
+ }
35
+ }
36
+
37
+ return {
38
+ ...component,
39
+ position: {
40
+ /**
41
+ * Get current position
42
+ * @returns {string} Current position
43
+ */
44
+ getPosition: () => position,
45
+
46
+ /**
47
+ * Set new position
48
+ * @param {string} newPosition - New position to set
49
+ * @returns {Object} Component instance
50
+ */
51
+ setPosition: (newPosition) => {
52
+ if (Object.values(SNACKBAR_POSITIONS).includes(newPosition)) {
53
+ setPosition(newPosition)
54
+ return component
55
+ } else {
56
+ console.warn(`Invalid position: ${newPosition}. Using default: ${SNACKBAR_POSITIONS.CENTER}`)
57
+ setPosition(SNACKBAR_POSITIONS.CENTER)
58
+ return component
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,74 @@
1
+ // src/components/snackbar/queue.js
2
+
3
+ /**
4
+ * Creates a queue manager for snackbars
5
+ * Ensures only one snackbar is visible at a time
6
+ */
7
+ export const createSnackbarQueue = () => {
8
+ const queue = []
9
+ let isProcessing = false
10
+
11
+ const processQueue = () => {
12
+ if (queue.length === 0) {
13
+ isProcessing = false
14
+ return
15
+ }
16
+
17
+ isProcessing = true
18
+ const snackbar = queue[0]
19
+
20
+ const handleDismiss = () => {
21
+ // Remove from queue
22
+ queue.shift()
23
+ // Remove listener and cleanup
24
+ snackbar.off?.('dismiss', handleDismiss)
25
+ // Reset processing state if queue is empty
26
+ if (queue.length === 0) {
27
+ isProcessing = false
28
+ } else {
29
+ // Process next after a small delay
30
+ setTimeout(processQueue, 200)
31
+ }
32
+ }
33
+
34
+ // Handle both normal dismiss and action button dismissal
35
+ snackbar.on?.('dismiss', handleDismiss)
36
+ snackbar._show()
37
+ }
38
+
39
+ return {
40
+ /**
41
+ * Adds a snackbar to the queue
42
+ * @param {Object} snackbar - Snackbar instance
43
+ */
44
+ add (snackbar) {
45
+ if (!snackbar._show) {
46
+ throw new Error('Snackbar must implement _show method')
47
+ }
48
+
49
+ queue.push(snackbar)
50
+
51
+ // Only start processing if not already processing
52
+ if (!isProcessing) {
53
+ processQueue()
54
+ }
55
+ },
56
+
57
+ /**
58
+ * Clears all pending snackbars
59
+ */
60
+ clear () {
61
+ // Remove all queued items
62
+ queue.length = 0
63
+ isProcessing = false
64
+ },
65
+
66
+ /**
67
+ * Gets current queue length
68
+ * @returns {number} Number of snackbars in queue
69
+ */
70
+ getLength () {
71
+ return queue.length
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,70 @@
1
+ // src/components/snackbar/snackbar.js
2
+ import { PREFIX } from '../../core/config'
3
+ import { pipe } from '../../core/compose'
4
+ import { createBase, withElement } from '../../core/compose/component'
5
+ import { withActionButton, withDismissTimer } from './features'
6
+ import { withPosition } from './position'
7
+ import {
8
+ withEvents,
9
+ withText,
10
+ withVariant,
11
+ withLifecycle
12
+ } from '../../core/compose/features'
13
+ import { withAPI } from './api'
14
+ import { createSnackbarQueue } from './queue'
15
+ import { SNACKBAR_VARIANTS, SNACKBAR_POSITIONS } from './constants'
16
+
17
+ // Create a single queue instance to be shared across all snackbars
18
+ const queue = createSnackbarQueue()
19
+
20
+ /**
21
+ * Creates a new Snackbar component
22
+ * @param {Object} config - Snackbar configuration
23
+ * @param {string} config.message - Message to display
24
+ * @param {string} [config.action] - Action button text
25
+ * @param {string} [config.variant='basic'] - Snackbar variant
26
+ * @param {string} [config.position='center'] - Display position
27
+ * @param {number} [config.duration=4000] - Display duration in ms
28
+ * @param {string} [config.class] - Additional CSS classes
29
+ */
30
+ const createSnackbar = (config = {}) => {
31
+ const baseConfig = {
32
+ ...config,
33
+ componentName: 'snackbar',
34
+ prefix: PREFIX,
35
+ variant: config.variant || SNACKBAR_VARIANTS.BASIC,
36
+ position: config.position || SNACKBAR_POSITIONS.CENTER,
37
+ duration: config.duration ?? 4000 // Use nullish coalescing to allow 0
38
+ }
39
+
40
+ try {
41
+ return pipe(
42
+ createBase,
43
+ withEvents(),
44
+ withElement({
45
+ tag: 'div',
46
+ componentName: 'snackbar',
47
+ className: config.class
48
+ }),
49
+ withVariant(baseConfig),
50
+ withPosition(baseConfig),
51
+ withText({
52
+ ...baseConfig,
53
+ text: config.message
54
+ }),
55
+ withActionButton(baseConfig),
56
+ withLifecycle(),
57
+ // First apply timer
58
+ withDismissTimer(baseConfig),
59
+ // Then apply API which needs timer
60
+ comp => withAPI({
61
+ lifecycle: comp.lifecycle,
62
+ queue
63
+ })(comp)
64
+ )(baseConfig)
65
+ } catch (error) {
66
+ throw new Error(`Failed to create snackbar: ${error.message}`)
67
+ }
68
+ }
69
+
70
+ export default createSnackbar