@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.
@@ -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,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
- const menu = (
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.map((item, index) => {
193
- return (
194
- <Menu.Item
195
- href={item.props.href ? item.props.href : undefined}
196
- onClick={
197
- item.props.onClick && !item.props.href
198
- ? item.props.onClick
199
- : undefined
200
- }
201
- key={index}
202
- >
203
- {callRenderProp(item.props.renderLabel)}
204
- </Menu.Item>
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 { children, visibleItemsCount, screenReaderLabel, margin } =
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
- <ul
249
- ref={(el) => (this._list = el)}
250
- 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}
251
159
  aria-label={callRenderProp(screenReaderLabel)}
252
160
  >
253
- {visibleChildren.map((child, index) => {
254
- return this.renderListItem(child, false, index)
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
  )
@@ -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
  ]