@instructure/ui-navigation 8.27.1-snapshot-2 → 8.27.1-snapshot-14
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/CHANGELOG.md +5 -2
- package/es/AppNav/index.js +17 -119
- package/es/AppNav/styles.js +4 -23
- package/lib/AppNav/index.js +19 -123
- package/lib/AppNav/styles.js +4 -23
- package/package.json +24 -23
- package/src/AppNav/index.tsx +39 -133
- package/src/AppNav/props.ts +4 -8
- package/src/AppNav/styles.ts +4 -23
- package/tsconfig.build.json +1 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/AppNav/index.d.ts +3 -13
- package/types/AppNav/index.d.ts.map +1 -1
- package/types/AppNav/props.d.ts +4 -1
- package/types/AppNav/props.d.ts.map +1 -1
- package/types/AppNav/styles.d.ts.map +1 -1
package/src/AppNav/index.tsx
CHANGED
|
@@ -23,15 +23,11 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
/** @jsx jsx */
|
|
26
|
-
import {
|
|
26
|
+
import React, { Component } from 'react'
|
|
27
27
|
|
|
28
28
|
import { withStyle, jsx } from '@instructure/emotion'
|
|
29
29
|
|
|
30
|
-
import { getBoundingClientRect } from '@instructure/ui-dom-utils'
|
|
31
30
|
import { callRenderProp, omitProps } from '@instructure/ui-react-utils'
|
|
32
|
-
import { px } from '@instructure/ui-utils'
|
|
33
|
-
import { debounce } from '@instructure/debounce'
|
|
34
|
-
import type { Debounced } from '@instructure/debounce'
|
|
35
31
|
import { testable } from '@instructure/ui-testable'
|
|
36
32
|
|
|
37
33
|
import { View } from '@instructure/ui-view'
|
|
@@ -44,6 +40,8 @@ import type { AppNavProps } from './props'
|
|
|
44
40
|
import { allowedProps, propTypes } from './props'
|
|
45
41
|
import { AppNavItemProps } from './Item/props'
|
|
46
42
|
|
|
43
|
+
import { TruncateList } from '@instructure/ui-truncate-list'
|
|
44
|
+
|
|
47
45
|
/**
|
|
48
46
|
---
|
|
49
47
|
category: components
|
|
@@ -73,89 +71,15 @@ class AppNav extends Component<AppNavProps> {
|
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
ref: Element | null = null
|
|
76
|
-
_list: HTMLUListElement | null = null
|
|
77
|
-
_debounced?: Debounced
|
|
78
|
-
_resizeListener?: ResizeObserver
|
|
79
74
|
|
|
80
75
|
componentDidMount() {
|
|
81
76
|
this.props.makeStyles?.()
|
|
82
|
-
const { width: origWidth } = getBoundingClientRect(this._list)
|
|
83
|
-
|
|
84
|
-
this._debounced = debounce(this.handleResize, this.props.debounce, {
|
|
85
|
-
leading: true,
|
|
86
|
-
trailing: true
|
|
87
|
-
})
|
|
88
|
-
this._resizeListener = new ResizeObserver((entries) => {
|
|
89
|
-
for (const entry of entries) {
|
|
90
|
-
const { width } = entry.contentRect
|
|
91
|
-
|
|
92
|
-
if (origWidth !== width) {
|
|
93
|
-
this._debounced!()
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
this._resizeListener.observe(this._list!)
|
|
99
|
-
|
|
100
|
-
this.handleResize()
|
|
101
77
|
}
|
|
102
78
|
|
|
103
79
|
componentDidUpdate() {
|
|
104
80
|
this.props.makeStyles?.()
|
|
105
81
|
}
|
|
106
82
|
|
|
107
|
-
componentWillUnmount() {
|
|
108
|
-
if (this._resizeListener) {
|
|
109
|
-
this._resizeListener.disconnect()
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (this._debounced) {
|
|
113
|
-
this._debounced.cancel()
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
measureItems = () => {
|
|
118
|
-
const menuTriggerWidth = px(this.props.styles?.menuTriggerWidth as number)
|
|
119
|
-
let visibleItemsCount = 0
|
|
120
|
-
|
|
121
|
-
if (this._list) {
|
|
122
|
-
const { width: navWidth } = getBoundingClientRect(this._list)
|
|
123
|
-
|
|
124
|
-
const itemWidths = Array.from(this._list.getElementsByTagName('li')).map(
|
|
125
|
-
(item) => {
|
|
126
|
-
// Todo: if item's type isn't `unknown`, can remove `Element`
|
|
127
|
-
const { width } = getBoundingClientRect(item as Element)
|
|
128
|
-
return width
|
|
129
|
-
}
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
let currentWidth = 0
|
|
133
|
-
|
|
134
|
-
for (let i = 0; i < itemWidths.length; i++) {
|
|
135
|
-
currentWidth += itemWidths[i]
|
|
136
|
-
|
|
137
|
-
if (currentWidth <= navWidth - menuTriggerWidth) {
|
|
138
|
-
visibleItemsCount++
|
|
139
|
-
} else {
|
|
140
|
-
break
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return { visibleItemsCount }
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
handleResize = () => {
|
|
149
|
-
this.setState({ isMeasuring: true }, () => {
|
|
150
|
-
const { visibleItemsCount } = this.measureItems()
|
|
151
|
-
|
|
152
|
-
if (typeof this.props.onUpdate === 'function') {
|
|
153
|
-
this.props.onUpdate({ visibleItemsCount })
|
|
154
|
-
}
|
|
155
|
-
this.setState({ isMeasuring: false })
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
|
|
159
83
|
handleRef = (el: Element | null) => {
|
|
160
84
|
const { elementRef } = this.props
|
|
161
85
|
|
|
@@ -166,22 +90,8 @@ class AppNav extends Component<AppNavProps> {
|
|
|
166
90
|
}
|
|
167
91
|
}
|
|
168
92
|
|
|
169
|
-
renderListItem(item: React.ReactNode, isMenuTrigger: boolean, key?: number) {
|
|
170
|
-
return (
|
|
171
|
-
<li
|
|
172
|
-
key={key}
|
|
173
|
-
css={
|
|
174
|
-
isMenuTrigger
|
|
175
|
-
? this.props.styles?.listItemWithMenuTrigger
|
|
176
|
-
: this.props.styles?.listItem
|
|
177
|
-
}
|
|
178
|
-
>
|
|
179
|
-
{item}
|
|
180
|
-
</li>
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
93
|
renderMenu(items: React.ComponentElement<AppNavItemProps, Item>[]) {
|
|
184
|
-
|
|
94
|
+
return (
|
|
185
95
|
<Menu
|
|
186
96
|
trigger={
|
|
187
97
|
<AppNav.Item
|
|
@@ -189,29 +99,29 @@ class AppNav extends Component<AppNavProps> {
|
|
|
189
99
|
/>
|
|
190
100
|
}
|
|
191
101
|
>
|
|
192
|
-
{items.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
102
|
+
{(items as React.ComponentElement<AppNavItemProps, Item>[]).map(
|
|
103
|
+
(item, index) => {
|
|
104
|
+
return (
|
|
105
|
+
<Menu.Item
|
|
106
|
+
href={item.props.href ? item.props.href : undefined}
|
|
107
|
+
onClick={
|
|
108
|
+
item.props.onClick && !item.props.href
|
|
109
|
+
? item.props.onClick
|
|
110
|
+
: undefined
|
|
111
|
+
}
|
|
112
|
+
key={index}
|
|
113
|
+
>
|
|
114
|
+
{callRenderProp(item.props.renderLabel)}
|
|
115
|
+
</Menu.Item>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
)}
|
|
207
119
|
</Menu>
|
|
208
120
|
)
|
|
209
|
-
|
|
210
|
-
return this.renderListItem(menu, true)
|
|
211
121
|
}
|
|
212
122
|
|
|
213
123
|
render() {
|
|
214
|
-
const {
|
|
124
|
+
const { visibleItemsCount, screenReaderLabel, margin, debounce, styles } =
|
|
215
125
|
this.props
|
|
216
126
|
|
|
217
127
|
const passthroughProps = View.omitViewProps(
|
|
@@ -219,15 +129,6 @@ class AppNav extends Component<AppNavProps> {
|
|
|
219
129
|
AppNav
|
|
220
130
|
)
|
|
221
131
|
|
|
222
|
-
const { isMeasuring } = this.state
|
|
223
|
-
const childrenArray = Children.toArray(children) as React.ComponentElement<
|
|
224
|
-
AppNavItemProps,
|
|
225
|
-
Item
|
|
226
|
-
>[]
|
|
227
|
-
const visibleChildren = isMeasuring
|
|
228
|
-
? childrenArray
|
|
229
|
-
: childrenArray.splice(0, visibleItemsCount)
|
|
230
|
-
const hiddenChildren = isMeasuring ? [] : childrenArray
|
|
231
132
|
const renderBeforeItems = callRenderProp(this.props.renderBeforeItems)
|
|
232
133
|
const renderAfterItems = callRenderProp(this.props.renderAfterItems)
|
|
233
134
|
const hasRenderedContent = renderBeforeItems || renderAfterItems
|
|
@@ -236,25 +137,30 @@ class AppNav extends Component<AppNavProps> {
|
|
|
236
137
|
<View
|
|
237
138
|
{...passthroughProps}
|
|
238
139
|
as="nav"
|
|
239
|
-
css={[
|
|
240
|
-
this.props.styles?.appNav,
|
|
241
|
-
hasRenderedContent ? this.props.styles?.alignCenter : ''
|
|
242
|
-
]}
|
|
140
|
+
css={[styles?.appNav, hasRenderedContent ? styles?.alignCenter : '']}
|
|
243
141
|
margin={margin}
|
|
244
142
|
display={hasRenderedContent ? 'flex' : 'block'}
|
|
245
143
|
elementRef={this.handleRef}
|
|
246
144
|
>
|
|
247
145
|
{renderBeforeItems && <span>{renderBeforeItems}</span>}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
146
|
+
|
|
147
|
+
<TruncateList
|
|
148
|
+
visibleItemsCount={visibleItemsCount}
|
|
149
|
+
debounce={debounce}
|
|
150
|
+
onUpdate={this.props.onUpdate}
|
|
151
|
+
renderHiddenItemMenu={(hiddenChildren) =>
|
|
152
|
+
this.renderMenu(
|
|
153
|
+
hiddenChildren as React.ComponentElement<AppNavItemProps, Item>[]
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
itemSpacing={styles?.horizontalMargin}
|
|
157
|
+
fixMenuTriggerWidth={styles?.menuTriggerWidth}
|
|
158
|
+
css={styles?.list}
|
|
251
159
|
aria-label={callRenderProp(screenReaderLabel)}
|
|
252
160
|
>
|
|
253
|
-
{
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
{hiddenChildren.length > 0 && this.renderMenu(hiddenChildren)}
|
|
257
|
-
</ul>
|
|
161
|
+
{this.props.children}
|
|
162
|
+
</TruncateList>
|
|
163
|
+
|
|
258
164
|
{renderAfterItems && <span>{renderAfterItems}</span>}
|
|
259
165
|
</View>
|
|
260
166
|
)
|
package/src/AppNav/props.ts
CHANGED
|
@@ -99,14 +99,10 @@ type AppNavProps = AppNavOwnProps &
|
|
|
99
99
|
WithStyleProps<AppNavTheme, AppNavStyle> &
|
|
100
100
|
OtherHTMLAttributes<AppNavOwnProps>
|
|
101
101
|
|
|
102
|
-
type AppNavStyle = ComponentStyle<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
| 'listItem'
|
|
107
|
-
| 'list'
|
|
108
|
-
| 'menuTriggerWidth'
|
|
109
|
-
>
|
|
102
|
+
type AppNavStyle = ComponentStyle<'appNav' | 'alignCenter' | 'list'> & {
|
|
103
|
+
horizontalMargin: string
|
|
104
|
+
menuTriggerWidth: string
|
|
105
|
+
}
|
|
110
106
|
|
|
111
107
|
const propTypes: PropValidators<PropKeys> = {
|
|
112
108
|
screenReaderLabel: PropTypes.string.isRequired,
|
package/src/AppNav/styles.ts
CHANGED
|
@@ -45,36 +45,17 @@ const generateStyle = (componentTheme: AppNavTheme): AppNavStyle => {
|
|
|
45
45
|
alignCenter: {
|
|
46
46
|
alignItems: 'center'
|
|
47
47
|
},
|
|
48
|
-
listItemWithMenuTrigger: {
|
|
49
|
-
label: 'appNav__listItemWithMenuTrigger',
|
|
50
|
-
minWidth: '0.0625rem',
|
|
51
|
-
marginInlineStart: componentTheme.horizontalMargin,
|
|
52
|
-
marginInlineEnd: '0',
|
|
53
|
-
flexShrink: 0,
|
|
54
|
-
flexBasis: componentTheme.menuTriggerWidth
|
|
55
|
-
},
|
|
56
|
-
listItem: {
|
|
57
|
-
label: 'appNav__listItem',
|
|
58
|
-
minWidth: '0.0625rem',
|
|
59
|
-
marginInlineStart: componentTheme.horizontalMargin,
|
|
60
|
-
marginInlineEnd: '0',
|
|
61
|
-
flexShrink: 0
|
|
62
|
-
},
|
|
63
48
|
list: {
|
|
64
49
|
label: 'appNav__list',
|
|
65
|
-
boxSizing: 'border-box',
|
|
66
|
-
listStyleType: 'none',
|
|
67
50
|
height: componentTheme.height,
|
|
68
|
-
margin: '0',
|
|
69
|
-
padding: '0',
|
|
70
|
-
display: 'flex',
|
|
71
|
-
alignItems: 'center',
|
|
72
51
|
flexGrow: 1,
|
|
73
52
|
flexShrink: 1,
|
|
74
53
|
flexBasis: '0',
|
|
75
|
-
minWidth: '0.0625rem'
|
|
54
|
+
minWidth: '0.0625rem',
|
|
55
|
+
paddingInlineStart: componentTheme.horizontalMargin
|
|
76
56
|
},
|
|
77
|
-
|
|
57
|
+
horizontalMargin: componentTheme.horizontalMargin.toString(),
|
|
58
|
+
menuTriggerWidth: componentTheme.menuTriggerWidth.toString()
|
|
78
59
|
}
|
|
79
60
|
}
|
|
80
61
|
|
package/tsconfig.build.json
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
{ "path": "../ui-react-utils/tsconfig.build.json" },
|
|
28
28
|
{ "path": "../ui-testable/tsconfig.build.json" },
|
|
29
29
|
{ "path": "../ui-tooltip/tsconfig.build.json" },
|
|
30
|
+
{ "path": "../ui-truncate-list/tsconfig.build.json" },
|
|
30
31
|
{ "path": "../ui-utils/tsconfig.build.json" },
|
|
31
32
|
{ "path": "../ui-view/tsconfig.build.json" }
|
|
32
33
|
]
|