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.
- package/LICENSE +21 -0
- package/README.md +251 -0
- package/index.js +10 -0
- package/package.json +17 -0
- package/src/components/button/api.js +54 -0
- package/src/components/button/button.js +81 -0
- package/src/components/button/config.js +8 -0
- package/src/components/button/constants.js +63 -0
- package/src/components/button/index.js +2 -0
- package/src/components/button/styles.scss +231 -0
- package/src/components/checkbox/api.js +45 -0
- package/src/components/checkbox/checkbox.js +95 -0
- package/src/components/checkbox/constants.js +88 -0
- package/src/components/checkbox/index.js +2 -0
- package/src/components/checkbox/styles.scss +183 -0
- package/src/components/container/api.js +42 -0
- package/src/components/container/container.js +45 -0
- package/src/components/container/index.js +2 -0
- package/src/components/container/styles.scss +59 -0
- package/src/components/list/constants.js +89 -0
- package/src/components/list/index.js +2 -0
- package/src/components/list/list-item.js +147 -0
- package/src/components/list/list.js +267 -0
- package/src/components/list/styles/_list-item.scss +142 -0
- package/src/components/list/styles/_list.scss +89 -0
- package/src/components/list/styles/_variables.scss +13 -0
- package/src/components/list/styles.scss +19 -0
- package/src/components/navigation/api.js +43 -0
- package/src/components/navigation/constants.js +235 -0
- package/src/components/navigation/features/items.js +192 -0
- package/src/components/navigation/index.js +2 -0
- package/src/components/navigation/nav-item.js +137 -0
- package/src/components/navigation/navigation.js +55 -0
- package/src/components/navigation/styles/_bar.scss +51 -0
- package/src/components/navigation/styles/_base.scss +129 -0
- package/src/components/navigation/styles/_drawer.scss +169 -0
- package/src/components/navigation/styles/_rail.scss +65 -0
- package/src/components/navigation/styles.scss +6 -0
- package/src/components/snackbar/api.js +125 -0
- package/src/components/snackbar/constants.js +41 -0
- package/src/components/snackbar/features.js +69 -0
- package/src/components/snackbar/index.js +2 -0
- package/src/components/snackbar/position.js +63 -0
- package/src/components/snackbar/queue.js +74 -0
- package/src/components/snackbar/snackbar.js +70 -0
- package/src/components/snackbar/styles.scss +182 -0
- package/src/components/switch/api.js +44 -0
- package/src/components/switch/constants.js +80 -0
- package/src/components/switch/index.js +2 -0
- package/src/components/switch/styles.scss +172 -0
- package/src/components/switch/switch.js +71 -0
- package/src/components/textfield/api.js +49 -0
- package/src/components/textfield/constants.js +81 -0
- package/src/components/textfield/index.js +2 -0
- package/src/components/textfield/styles/base.scss +107 -0
- package/src/components/textfield/styles/filled.scss +58 -0
- package/src/components/textfield/styles/outlined.scss +66 -0
- package/src/components/textfield/styles.scss +6 -0
- package/src/components/textfield/textfield.js +68 -0
- package/src/core/build/constants.js +51 -0
- package/src/core/build/icon.js +78 -0
- package/src/core/build/ripple.js +92 -0
- package/src/core/build/text.js +54 -0
- package/src/core/collection/adapters/base.js +26 -0
- package/src/core/collection/adapters/mongodb.js +232 -0
- package/src/core/collection/adapters/route.js +201 -0
- package/src/core/collection/collection.js +259 -0
- package/src/core/collection/list-manager.js +157 -0
- package/src/core/compose/base.js +8 -0
- package/src/core/compose/component.js +225 -0
- package/src/core/compose/features/checkable.js +114 -0
- package/src/core/compose/features/disabled.js +25 -0
- package/src/core/compose/features/events.js +48 -0
- package/src/core/compose/features/icon.js +33 -0
- package/src/core/compose/features/index.js +20 -0
- package/src/core/compose/features/input.js +92 -0
- package/src/core/compose/features/lifecycle.js +69 -0
- package/src/core/compose/features/position.js +60 -0
- package/src/core/compose/features/ripple.js +32 -0
- package/src/core/compose/features/size.js +9 -0
- package/src/core/compose/features/style.js +12 -0
- package/src/core/compose/features/text.js +17 -0
- package/src/core/compose/features/textinput.js +118 -0
- package/src/core/compose/features/textlabel.js +28 -0
- package/src/core/compose/features/track.js +49 -0
- package/src/core/compose/features/variant.js +9 -0
- package/src/core/compose/features/withEvents.js +67 -0
- package/src/core/compose/index.js +16 -0
- package/src/core/compose/pipe.js +69 -0
- package/src/core/config.js +140 -0
- package/src/core/dom/attributes.js +33 -0
- package/src/core/dom/classes.js +70 -0
- package/src/core/dom/create.js +133 -0
- package/src/core/dom/events.js +175 -0
- package/src/core/dom/index.js +5 -0
- package/src/core/dom/utils.js +22 -0
- package/src/core/index.js +23 -0
- package/src/core/layout/index.js +93 -0
- package/src/core/state/disabled.js +14 -0
- package/src/core/state/emitter.js +63 -0
- package/src/core/state/events.js +29 -0
- package/src/core/state/index.js +6 -0
- package/src/core/state/lifecycle.js +64 -0
- package/src/core/state/store.js +112 -0
- package/src/core/utils/index.js +39 -0
- package/src/core/utils/mobile.js +74 -0
- package/src/core/utils/object.js +22 -0
- package/src/core/utils/validate.js +37 -0
- package/src/index.js +11 -0
- package/src/styles/abstract/_base.scss +2 -0
- package/src/styles/abstract/_config.scss +28 -0
- package/src/styles/abstract/_functions.scss +124 -0
- package/src/styles/abstract/_mixins.scss +261 -0
- package/src/styles/abstract/_variables.scss +158 -0
- package/src/styles/main.scss +78 -0
- package/src/styles/themes/_base-theme.scss +49 -0
- package/src/styles/themes/_baseline.scss +90 -0
- package/src/styles/themes/_forest.scss +71 -0
- package/src/styles/themes/_index.scss +6 -0
- package/src/styles/themes/_ocean.scss +71 -0
- package/src/styles/themes/_sunset.scss +55 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// src/components/navigation/features/items.js
|
|
2
|
+
import { createNavItem, getAllNestedItems } from '../nav-item'
|
|
3
|
+
|
|
4
|
+
export const withNavItems = (config) => (component) => {
|
|
5
|
+
const items = new Map()
|
|
6
|
+
let activeItem = null
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Recursively stores items in the items Map
|
|
10
|
+
* @param {Object} itemConfig - Item configuration
|
|
11
|
+
* @param {HTMLElement} item - Created item element
|
|
12
|
+
*/
|
|
13
|
+
const storeItem = (itemConfig, item) => {
|
|
14
|
+
items.set(itemConfig.id, { element: item, config: itemConfig })
|
|
15
|
+
|
|
16
|
+
if (itemConfig.items?.length) {
|
|
17
|
+
itemConfig.items.forEach(nestedConfig => {
|
|
18
|
+
const container = item.closest(`.${config.prefix}-nav-item-container`)
|
|
19
|
+
const nestedContainer = container.querySelector(`.${config.prefix}-nav-nested-container`)
|
|
20
|
+
const nestedItem = nestedContainer.querySelector(`[data-id="${nestedConfig.id}"]`)
|
|
21
|
+
storeItem(nestedConfig, nestedItem)
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Create initial items
|
|
27
|
+
if (config.items) {
|
|
28
|
+
config.items.forEach(itemConfig => {
|
|
29
|
+
const item = createNavItem(itemConfig, component.element, config.prefix)
|
|
30
|
+
storeItem(itemConfig, item)
|
|
31
|
+
|
|
32
|
+
if (itemConfig.active) {
|
|
33
|
+
activeItem = { element: item, config: itemConfig }
|
|
34
|
+
item.classList.add(`${config.prefix}-nav-item--active`)
|
|
35
|
+
item.setAttribute('aria-selected', 'true')
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Handle item clicks
|
|
41
|
+
component.element.addEventListener('click', (event) => {
|
|
42
|
+
const item = event.target.closest(`.${config.prefix}-nav-item`)
|
|
43
|
+
if (!item || item.disabled || item.getAttribute('aria-haspopup') === 'true') return
|
|
44
|
+
|
|
45
|
+
const id = item.dataset.id
|
|
46
|
+
const itemData = items.get(id)
|
|
47
|
+
if (!itemData) return
|
|
48
|
+
|
|
49
|
+
// Store previous item before updating
|
|
50
|
+
const previousItem = activeItem
|
|
51
|
+
|
|
52
|
+
// Update active state
|
|
53
|
+
if (activeItem) {
|
|
54
|
+
activeItem.element.classList.remove(`${config.prefix}-nav-item--active`)
|
|
55
|
+
activeItem.element.setAttribute('aria-selected', 'false')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
item.classList.add(`${config.prefix}-nav-item--active`)
|
|
59
|
+
item.setAttribute('aria-selected', 'true')
|
|
60
|
+
activeItem = itemData
|
|
61
|
+
|
|
62
|
+
// Emit change event with item data
|
|
63
|
+
component.emit('change', {
|
|
64
|
+
id,
|
|
65
|
+
item: itemData,
|
|
66
|
+
previousItem,
|
|
67
|
+
path: getItemPath(id)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Gets the path to an item (parent IDs)
|
|
73
|
+
* @param {string} id - Item ID to get path for
|
|
74
|
+
* @returns {Array<string>} Array of parent item IDs
|
|
75
|
+
*/
|
|
76
|
+
const getItemPath = (id) => {
|
|
77
|
+
const path = []
|
|
78
|
+
let currentItem = items.get(id)
|
|
79
|
+
|
|
80
|
+
while (currentItem) {
|
|
81
|
+
const parentContainer = currentItem.element.closest(`.${config.prefix}-nav-nested-container`)
|
|
82
|
+
if (!parentContainer) break
|
|
83
|
+
|
|
84
|
+
const parentItem = parentContainer.previousElementSibling
|
|
85
|
+
if (!parentItem) break
|
|
86
|
+
|
|
87
|
+
const parentId = parentItem.dataset.id
|
|
88
|
+
if (!parentId) break
|
|
89
|
+
|
|
90
|
+
path.unshift(parentId)
|
|
91
|
+
currentItem = items.get(parentId)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return path
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Clean up when component is destroyed
|
|
98
|
+
if (component.lifecycle) {
|
|
99
|
+
const originalDestroy = component.lifecycle.destroy
|
|
100
|
+
component.lifecycle.destroy = () => {
|
|
101
|
+
items.clear()
|
|
102
|
+
originalDestroy?.()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
...component,
|
|
108
|
+
items,
|
|
109
|
+
|
|
110
|
+
addItem (itemConfig) {
|
|
111
|
+
if (items.has(itemConfig.id)) return this
|
|
112
|
+
|
|
113
|
+
const item = createNavItem(itemConfig, component.element, config.prefix)
|
|
114
|
+
storeItem(itemConfig, item)
|
|
115
|
+
|
|
116
|
+
if (itemConfig.active) {
|
|
117
|
+
this.setActive(itemConfig.id)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
component.emit('itemAdded', {
|
|
121
|
+
id: itemConfig.id,
|
|
122
|
+
item: { element: item, config: itemConfig }
|
|
123
|
+
})
|
|
124
|
+
return this
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
removeItem (id) {
|
|
128
|
+
const item = items.get(id)
|
|
129
|
+
if (!item) return this
|
|
130
|
+
|
|
131
|
+
// Remove all nested items first
|
|
132
|
+
const nestedItems = getAllNestedItems(item.element, config.prefix)
|
|
133
|
+
nestedItems.forEach(nestedItem => {
|
|
134
|
+
const nestedId = nestedItem.dataset.id
|
|
135
|
+
if (nestedId) items.delete(nestedId)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
if (activeItem?.config.id === id) {
|
|
139
|
+
activeItem = null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Remove the entire item container
|
|
143
|
+
const container = item.element.closest(`.${config.prefix}-nav-item-container`)
|
|
144
|
+
container?.remove()
|
|
145
|
+
items.delete(id)
|
|
146
|
+
|
|
147
|
+
component.emit('itemRemoved', { id, item })
|
|
148
|
+
return this
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
getItem: (id) => items.get(id),
|
|
152
|
+
getAllItems: () => Array.from(items.values()),
|
|
153
|
+
getActive: () => activeItem,
|
|
154
|
+
getItemPath: (id) => getItemPath(id),
|
|
155
|
+
|
|
156
|
+
setActive (id) {
|
|
157
|
+
const item = items.get(id)
|
|
158
|
+
if (!item || item.config.disabled) return this
|
|
159
|
+
|
|
160
|
+
if (activeItem) {
|
|
161
|
+
activeItem.element.classList.remove(`${config.prefix}-nav-item--active`)
|
|
162
|
+
activeItem.element.setAttribute('aria-selected', 'false')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
item.element.classList.add(`${config.prefix}-nav-item--active`)
|
|
166
|
+
item.element.setAttribute('aria-selected', 'true')
|
|
167
|
+
activeItem = item
|
|
168
|
+
|
|
169
|
+
// Ensure all parent items are expanded
|
|
170
|
+
const path = getItemPath(id)
|
|
171
|
+
path.forEach(parentId => {
|
|
172
|
+
const parentItem = items.get(parentId)
|
|
173
|
+
if (parentItem) {
|
|
174
|
+
const parentButton = parentItem.element
|
|
175
|
+
const nestedContainer = parentButton.closest(`.${config.prefix}-nav-item-container`)
|
|
176
|
+
.querySelector(`.${config.prefix}-nav-nested-container`)
|
|
177
|
+
|
|
178
|
+
parentButton.setAttribute('aria-expanded', 'true')
|
|
179
|
+
nestedContainer.hidden = false
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
component.emit('activeChanged', {
|
|
184
|
+
id,
|
|
185
|
+
item,
|
|
186
|
+
previousItem: activeItem,
|
|
187
|
+
path: getItemPath(id)
|
|
188
|
+
})
|
|
189
|
+
return this
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/components/navigation/nav-item.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates an expand/collapse icon element
|
|
5
|
+
* @param {string} prefix - CSS class prefix
|
|
6
|
+
* @returns {HTMLElement} Expand icon element
|
|
7
|
+
*/
|
|
8
|
+
const createExpandIcon = (prefix) => {
|
|
9
|
+
const icon = document.createElement('span')
|
|
10
|
+
icon.className = `${prefix}-nav-expand-icon`
|
|
11
|
+
icon.innerHTML = `
|
|
12
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
13
|
+
<polyline points="9 18 15 12 9 6"></polyline>
|
|
14
|
+
</svg>
|
|
15
|
+
`
|
|
16
|
+
return icon
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a nested items container
|
|
21
|
+
* @param {Array} items - Nested items configuration
|
|
22
|
+
* @param {string} prefix - CSS class prefix
|
|
23
|
+
* @param {Function} createItem - Item creation function
|
|
24
|
+
* @returns {HTMLElement} Nested items container
|
|
25
|
+
*/
|
|
26
|
+
const createNestedContainer = (items, prefix, createItem) => {
|
|
27
|
+
const container = document.createElement('div')
|
|
28
|
+
container.className = `${prefix}-nav-nested-container`
|
|
29
|
+
container.setAttribute('role', 'group')
|
|
30
|
+
container.hidden = true
|
|
31
|
+
|
|
32
|
+
items.forEach(itemConfig => {
|
|
33
|
+
createItem(itemConfig, container, prefix)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return container
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a navigation item element
|
|
41
|
+
* @param {Object} config - Item configuration
|
|
42
|
+
* @param {HTMLElement} container - Container element
|
|
43
|
+
* @param {string} prefix - CSS class prefix
|
|
44
|
+
* @returns {HTMLElement} Created navigation item
|
|
45
|
+
*/
|
|
46
|
+
export const createNavItem = (config, container, prefix) => {
|
|
47
|
+
const itemContainer = document.createElement('div')
|
|
48
|
+
itemContainer.className = `${prefix}-nav-item-container`
|
|
49
|
+
|
|
50
|
+
const item = document.createElement('button')
|
|
51
|
+
item.className = `${prefix}-nav-item`
|
|
52
|
+
item.setAttribute('role', config.items?.length ? 'button' : 'menuitem')
|
|
53
|
+
item.setAttribute('aria-selected', 'false')
|
|
54
|
+
|
|
55
|
+
if (config.id) {
|
|
56
|
+
item.dataset.id = config.id
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (config.disabled) {
|
|
60
|
+
item.disabled = true
|
|
61
|
+
item.setAttribute('aria-disabled', 'true')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Add icon if provided
|
|
65
|
+
if (config.icon) {
|
|
66
|
+
const icon = document.createElement('span')
|
|
67
|
+
icon.className = `${prefix}-nav-item-icon`
|
|
68
|
+
icon.innerHTML = config.icon
|
|
69
|
+
item.appendChild(icon)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Add label if provided
|
|
73
|
+
if (config.label) {
|
|
74
|
+
const label = document.createElement('span')
|
|
75
|
+
label.className = `${prefix}-nav-item-label`
|
|
76
|
+
label.textContent = config.label
|
|
77
|
+
item.appendChild(label)
|
|
78
|
+
item.setAttribute('aria-label', config.label)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Add badge if provided
|
|
82
|
+
if (config.badge) {
|
|
83
|
+
const badge = document.createElement('span')
|
|
84
|
+
badge.className = `${prefix}-nav-item-badge`
|
|
85
|
+
badge.textContent = config.badge
|
|
86
|
+
badge.setAttribute('aria-label', `${config.badge} notifications`)
|
|
87
|
+
item.appendChild(badge)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
itemContainer.appendChild(item)
|
|
91
|
+
|
|
92
|
+
// Handle nested items - only for drawer variant
|
|
93
|
+
if (config.items?.length && container.closest('.mtrl-nav--drawer, .mtrl-nav--drawer-modal, .mtrl-nav--drawer-standard')) {
|
|
94
|
+
const expandIcon = createExpandIcon(prefix)
|
|
95
|
+
item.appendChild(expandIcon)
|
|
96
|
+
|
|
97
|
+
item.setAttribute('aria-expanded', config.expanded ? 'true' : 'false')
|
|
98
|
+
item.setAttribute('aria-haspopup', 'true')
|
|
99
|
+
|
|
100
|
+
const nestedContainer = createNestedContainer(config.items, prefix, createNavItem)
|
|
101
|
+
nestedContainer.hidden = !config.expanded
|
|
102
|
+
itemContainer.appendChild(nestedContainer)
|
|
103
|
+
|
|
104
|
+
// Handle expand/collapse
|
|
105
|
+
item.addEventListener('click', (event) => {
|
|
106
|
+
event.stopPropagation()
|
|
107
|
+
const isExpanded = item.getAttribute('aria-expanded') === 'true'
|
|
108
|
+
item.setAttribute('aria-expanded', (!isExpanded).toString())
|
|
109
|
+
nestedContainer.hidden = isExpanded
|
|
110
|
+
|
|
111
|
+
// Toggle expand icon rotation
|
|
112
|
+
expandIcon.style.transform = isExpanded ? '' : 'rotate(90deg)'
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
container.appendChild(itemContainer)
|
|
117
|
+
return item
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Recursively gets all nested items from a navigation item
|
|
122
|
+
* @param {HTMLElement} item - Navigation item element
|
|
123
|
+
* @param {string} prefix - CSS class prefix
|
|
124
|
+
* @returns {Array<HTMLElement>} Array of all nested items
|
|
125
|
+
*/
|
|
126
|
+
export const getAllNestedItems = (item, prefix) => {
|
|
127
|
+
const container = item.closest(`.${prefix}-nav-item-container`)
|
|
128
|
+
if (!container) return []
|
|
129
|
+
|
|
130
|
+
const nestedContainer = container.querySelector(`.${prefix}-nav-nested-container`)
|
|
131
|
+
if (!nestedContainer) return []
|
|
132
|
+
|
|
133
|
+
const items = Array.from(nestedContainer.querySelectorAll(`.${prefix}-nav-item`))
|
|
134
|
+
return items.reduce((acc, nestedItem) => {
|
|
135
|
+
return [...acc, nestedItem, ...getAllNestedItems(nestedItem, prefix)]
|
|
136
|
+
}, [])
|
|
137
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/components/navigation/index.js
|
|
2
|
+
import { PREFIX } from '../../core/config'
|
|
3
|
+
import { pipe } from '../../core/compose'
|
|
4
|
+
import { createBase, withElement } from '../../core/compose/component'
|
|
5
|
+
import {
|
|
6
|
+
withEvents,
|
|
7
|
+
withDisabled,
|
|
8
|
+
withLifecycle,
|
|
9
|
+
withVariant,
|
|
10
|
+
withPosition // Import core position feature
|
|
11
|
+
} from '../../core/compose/features'
|
|
12
|
+
import { withAPI } from './api'
|
|
13
|
+
import { withNavItems } from './features/items'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new Navigation component
|
|
17
|
+
* @param {Object} config - Navigation configuration
|
|
18
|
+
* @param {string} [config.variant='rail'] - Navigation type (rail/drawer/bar)
|
|
19
|
+
* @param {string} [config.position='left'] - Navigation position
|
|
20
|
+
* @param {Array} [config.items=[]] - Navigation items
|
|
21
|
+
* @param {boolean} [config.disabled=false] - Is navigation disabled
|
|
22
|
+
* @param {string} [config.class] - Additional CSS classes
|
|
23
|
+
*/
|
|
24
|
+
const createNavigation = (config = {}) => {
|
|
25
|
+
const baseConfig = {
|
|
26
|
+
...config,
|
|
27
|
+
componentName: 'nav',
|
|
28
|
+
prefix: PREFIX
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return pipe(
|
|
32
|
+
createBase,
|
|
33
|
+
// First add events system
|
|
34
|
+
withEvents(),
|
|
35
|
+
// Then add the element and other features
|
|
36
|
+
withElement({
|
|
37
|
+
tag: 'nav',
|
|
38
|
+
role: 'navigation',
|
|
39
|
+
'aria-label': config.ariaLabel || 'Main Navigation',
|
|
40
|
+
componentName: 'nav',
|
|
41
|
+
className: config.class
|
|
42
|
+
}),
|
|
43
|
+
withVariant(baseConfig),
|
|
44
|
+
withPosition(baseConfig),
|
|
45
|
+
withNavItems(baseConfig),
|
|
46
|
+
withDisabled(),
|
|
47
|
+
withLifecycle(),
|
|
48
|
+
comp => withAPI({
|
|
49
|
+
disabled: comp.disabled,
|
|
50
|
+
lifecycle: comp.lifecycle
|
|
51
|
+
})(comp)
|
|
52
|
+
)(baseConfig)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default createNavigation
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/components/navigation/styles/_bar.scss
|
|
2
|
+
@use 'sass:map';
|
|
3
|
+
@use '../../../styles/abstract/config' as c;
|
|
4
|
+
@use 'base';
|
|
5
|
+
|
|
6
|
+
.#{c.$prefix}-nav {
|
|
7
|
+
&--bar {
|
|
8
|
+
flex-direction: row;
|
|
9
|
+
width: 100%;
|
|
10
|
+
height: 80px;
|
|
11
|
+
padding: 0 12px;
|
|
12
|
+
justify-content: space-around;
|
|
13
|
+
|
|
14
|
+
.#{c.$prefix}-nav-item {
|
|
15
|
+
flex: 1;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
height: 100%;
|
|
18
|
+
max-width: 168px;
|
|
19
|
+
gap: 4px;
|
|
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: 16px;
|
|
36
|
+
@include c.motion-transition(background-color);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&-label {
|
|
40
|
+
@include c.typography('label-medium');
|
|
41
|
+
text-align: center;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&-badge {
|
|
45
|
+
top: 4px;
|
|
46
|
+
right: 50%;
|
|
47
|
+
transform: translateX(12px);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// src/components/navigation/styles/_base.scss
|
|
2
|
+
@use 'sass:map';
|
|
3
|
+
@use '../../../styles/abstract/config' as c;
|
|
4
|
+
|
|
5
|
+
.#{c.$prefix}-nav {
|
|
6
|
+
display: flex;
|
|
7
|
+
position: relative;
|
|
8
|
+
background-color: var(--mtrl-sys-color-surface-container);
|
|
9
|
+
|
|
10
|
+
// Base nav item styles
|
|
11
|
+
&-item {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
position: relative;
|
|
16
|
+
border: none;
|
|
17
|
+
background: none;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
padding: 12px;
|
|
20
|
+
gap: 12px;
|
|
21
|
+
color: var(--mtrl-sys-color-on-surface-variant);
|
|
22
|
+
@include c.motion-transition(all);
|
|
23
|
+
|
|
24
|
+
&-icon {
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
width: 24px;
|
|
29
|
+
height: 24px;
|
|
30
|
+
color: inherit;
|
|
31
|
+
border-radius: 50%;
|
|
32
|
+
padding: 8px;
|
|
33
|
+
|
|
34
|
+
svg {
|
|
35
|
+
width: 24px;
|
|
36
|
+
height: 24px;
|
|
37
|
+
fill: currentColor;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&--active {
|
|
42
|
+
color: var(--mtrl-sys-color-primary);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&-label {
|
|
46
|
+
@include c.typography('label-large');
|
|
47
|
+
@include c.motion-transition(opacity);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&-badge {
|
|
51
|
+
position: absolute;
|
|
52
|
+
top: 8px;
|
|
53
|
+
right: 8px;
|
|
54
|
+
min-width: 16px;
|
|
55
|
+
height: 16px;
|
|
56
|
+
padding: 0 4px;
|
|
57
|
+
border-radius: 8px;
|
|
58
|
+
background-color: var(--mtrl-sys-color-error);
|
|
59
|
+
color: var(--mtrl-sys-color-on-error);
|
|
60
|
+
@include c.typography('label-small');
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Navigation positions
|
|
68
|
+
&--left {
|
|
69
|
+
left: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
&--right {
|
|
73
|
+
right: 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&--top {
|
|
77
|
+
top: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&--bottom {
|
|
81
|
+
bottom: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// States
|
|
85
|
+
&--disabled {
|
|
86
|
+
opacity: 0.38;
|
|
87
|
+
pointer-events: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// RTL Support
|
|
91
|
+
@include c.rtl {
|
|
92
|
+
&--left {
|
|
93
|
+
right: 0;
|
|
94
|
+
left: auto;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
&--right {
|
|
98
|
+
left: 0;
|
|
99
|
+
right: auto;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.#{c.$prefix}-nav-item {
|
|
103
|
+
&-badge {
|
|
104
|
+
right: auto;
|
|
105
|
+
left: 8px;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Motion
|
|
111
|
+
@include c.reduced-motion {
|
|
112
|
+
&-item,
|
|
113
|
+
&-item-label {
|
|
114
|
+
transition: none;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// High contrast
|
|
119
|
+
@include c.high-contrast {
|
|
120
|
+
border: 1px solid currentColor;
|
|
121
|
+
|
|
122
|
+
&-item {
|
|
123
|
+
&--active {
|
|
124
|
+
outline: 2px solid currentColor;
|
|
125
|
+
outline-offset: -2px;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|