@instructure/ui-navigation 8.27.1-snapshot-7 → 8.27.1-snapshot-13

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.
@@ -23,15 +23,11 @@
23
23
  */
24
24
 
25
25
  /** @jsx jsx */
26
- import { Children, Component } from 'react'
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,88 +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
- const { width } = getBoundingClientRect(item)
127
- return width
128
- }
129
- )
130
-
131
- let currentWidth = 0
132
-
133
- for (let i = 0; i < itemWidths.length; i++) {
134
- currentWidth += itemWidths[i]
135
-
136
- if (currentWidth <= navWidth - menuTriggerWidth) {
137
- visibleItemsCount++
138
- } else {
139
- break
140
- }
141
- }
142
- }
143
-
144
- return { visibleItemsCount }
145
- }
146
-
147
- handleResize = () => {
148
- this.setState({ isMeasuring: true }, () => {
149
- const { visibleItemsCount } = this.measureItems()
150
-
151
- if (typeof this.props.onUpdate === 'function') {
152
- this.props.onUpdate({ visibleItemsCount })
153
- }
154
- this.setState({ isMeasuring: false })
155
- })
156
- }
157
-
158
83
  handleRef = (el: Element | null) => {
159
84
  const { elementRef } = this.props
160
85
 
@@ -165,22 +90,8 @@ class AppNav extends Component<AppNavProps> {
165
90
  }
166
91
  }
167
92
 
168
- renderListItem(item: React.ReactNode, isMenuTrigger: boolean, key?: number) {
169
- return (
170
- <li
171
- key={key}
172
- css={
173
- isMenuTrigger
174
- ? this.props.styles?.listItemWithMenuTrigger
175
- : this.props.styles?.listItem
176
- }
177
- >
178
- {item}
179
- </li>
180
- )
181
- }
182
93
  renderMenu(items: React.ComponentElement<AppNavItemProps, Item>[]) {
183
- const menu = (
94
+ return (
184
95
  <Menu
185
96
  trigger={
186
97
  <AppNav.Item
@@ -188,29 +99,29 @@ class AppNav extends Component<AppNavProps> {
188
99
  />
189
100
  }
190
101
  >
191
- {items.map((item, index) => {
192
- return (
193
- <Menu.Item
194
- href={item.props.href ? item.props.href : undefined}
195
- onClick={
196
- item.props.onClick && !item.props.href
197
- ? item.props.onClick
198
- : undefined
199
- }
200
- key={index}
201
- >
202
- {callRenderProp(item.props.renderLabel)}
203
- </Menu.Item>
204
- )
205
- })}
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
+ )}
206
119
  </Menu>
207
120
  )
208
-
209
- return this.renderListItem(menu, true)
210
121
  }
211
122
 
212
123
  render() {
213
- const { children, visibleItemsCount, screenReaderLabel, margin } =
124
+ const { visibleItemsCount, screenReaderLabel, margin, debounce, styles } =
214
125
  this.props
215
126
 
216
127
  const passthroughProps = View.omitViewProps(
@@ -218,15 +129,6 @@ class AppNav extends Component<AppNavProps> {
218
129
  AppNav
219
130
  )
220
131
 
221
- const { isMeasuring } = this.state
222
- const childrenArray = Children.toArray(children) as React.ComponentElement<
223
- AppNavItemProps,
224
- Item
225
- >[]
226
- const visibleChildren = isMeasuring
227
- ? childrenArray
228
- : childrenArray.splice(0, visibleItemsCount)
229
- const hiddenChildren = isMeasuring ? [] : childrenArray
230
132
  const renderBeforeItems = callRenderProp(this.props.renderBeforeItems)
231
133
  const renderAfterItems = callRenderProp(this.props.renderAfterItems)
232
134
  const hasRenderedContent = renderBeforeItems || renderAfterItems
@@ -235,25 +137,30 @@ class AppNav extends Component<AppNavProps> {
235
137
  <View
236
138
  {...passthroughProps}
237
139
  as="nav"
238
- css={[
239
- this.props.styles?.appNav,
240
- hasRenderedContent ? this.props.styles?.alignCenter : ''
241
- ]}
140
+ css={[styles?.appNav, hasRenderedContent ? styles?.alignCenter : '']}
242
141
  margin={margin}
243
142
  display={hasRenderedContent ? 'flex' : 'block'}
244
143
  elementRef={this.handleRef}
245
144
  >
246
145
  {renderBeforeItems && <span>{renderBeforeItems}</span>}
247
- <ul
248
- ref={(el) => (this._list = el)}
249
- css={this.props.styles?.list}
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}
250
159
  aria-label={callRenderProp(screenReaderLabel)}
251
160
  >
252
- {visibleChildren.map((child, index) => {
253
- return this.renderListItem(child, false, index)
254
- })}
255
- {hiddenChildren.length > 0 && this.renderMenu(hiddenChildren)}
256
- </ul>
161
+ {this.props.children}
162
+ </TruncateList>
163
+
257
164
  {renderAfterItems && <span>{renderAfterItems}</span>}
258
165
  </View>
259
166
  )
@@ -99,14 +99,10 @@ type AppNavProps = AppNavOwnProps &
99
99
  WithStyleProps<AppNavTheme, AppNavStyle> &
100
100
  OtherHTMLAttributes<AppNavOwnProps>
101
101
 
102
- type AppNavStyle = ComponentStyle<
103
- | 'appNav'
104
- | 'alignCenter'
105
- | 'listItemWithMenuTrigger'
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,
@@ -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
- menuTriggerWidth: componentTheme.menuTriggerWidth
57
+ horizontalMargin: componentTheme.horizontalMargin.toString(),
58
+ menuTriggerWidth: componentTheme.menuTriggerWidth.toString()
78
59
  }
79
60
  }
80
61
 
@@ -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
  ]