@instructure/ui-tree-browser 11.6.0 → 11.6.1-snapshot-129

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 (168) hide show
  1. package/CHANGELOG.md +33 -288
  2. package/es/TreeBrowser/{TreeButton → v1/TreeButton}/index.js +2 -2
  3. package/es/TreeBrowser/{TreeCollection → v1/TreeCollection}/index.js +1 -1
  4. package/es/TreeBrowser/{TreeNode → v1/TreeNode}/index.js +2 -2
  5. package/es/TreeBrowser/{index.js → v1/index.js} +1 -1
  6. package/es/TreeBrowser/v2/TreeBrowserContext.js +39 -0
  7. package/es/TreeBrowser/v2/TreeButton/index.js +202 -0
  8. package/es/TreeBrowser/v2/TreeButton/props.js +26 -0
  9. package/es/TreeBrowser/v2/TreeButton/styles.js +306 -0
  10. package/es/TreeBrowser/v2/TreeCollection/index.js +367 -0
  11. package/es/TreeBrowser/v2/TreeCollection/props.js +26 -0
  12. package/es/TreeBrowser/v2/TreeCollection/styles.js +143 -0
  13. package/es/TreeBrowser/v2/TreeNode/index.js +156 -0
  14. package/es/{index.js → TreeBrowser/v2/TreeNode/props.js} +3 -4
  15. package/es/TreeBrowser/v2/index.js +347 -0
  16. package/es/TreeBrowser/v2/props.js +35 -0
  17. package/es/TreeBrowser/v2/styles.js +71 -0
  18. package/es/exports/a.js +27 -0
  19. package/es/exports/b.js +27 -0
  20. package/lib/TreeBrowser/{TreeButton → v1/TreeButton}/index.js +3 -3
  21. package/lib/TreeBrowser/{TreeCollection → v1/TreeCollection}/index.js +1 -1
  22. package/lib/TreeBrowser/{TreeNode → v1/TreeNode}/index.js +3 -3
  23. package/lib/TreeBrowser/{index.js → v1/index.js} +3 -3
  24. package/lib/TreeBrowser/v2/TreeBrowserContext.js +44 -0
  25. package/lib/TreeBrowser/v2/TreeButton/index.js +208 -0
  26. package/lib/TreeBrowser/v2/TreeButton/props.js +31 -0
  27. package/lib/TreeBrowser/v2/TreeButton/styles.js +312 -0
  28. package/lib/TreeBrowser/v2/TreeCollection/index.js +372 -0
  29. package/lib/TreeBrowser/v2/TreeCollection/props.js +31 -0
  30. package/lib/TreeBrowser/v2/TreeCollection/styles.js +149 -0
  31. package/lib/TreeBrowser/v2/TreeNode/index.js +162 -0
  32. package/lib/TreeBrowser/v2/TreeNode/props.js +31 -0
  33. package/lib/TreeBrowser/v2/index.js +353 -0
  34. package/lib/TreeBrowser/v2/props.js +40 -0
  35. package/lib/TreeBrowser/v2/styles.js +77 -0
  36. package/lib/{index.js → exports/a.js} +5 -5
  37. package/lib/exports/b.js +33 -0
  38. package/package.json +40 -18
  39. package/src/TreeBrowser/{TreeButton → v1/TreeButton}/index.tsx +2 -2
  40. package/src/TreeBrowser/{TreeCollection → v1/TreeCollection}/index.tsx +1 -1
  41. package/src/TreeBrowser/{TreeNode → v1/TreeNode}/index.tsx +2 -2
  42. package/src/TreeBrowser/{index.tsx → v1/index.tsx} +1 -1
  43. package/src/TreeBrowser/v2/README.md +712 -0
  44. package/src/TreeBrowser/v2/TreeBrowserContext.ts +53 -0
  45. package/src/TreeBrowser/v2/TreeButton/index.tsx +210 -0
  46. package/src/TreeBrowser/v2/TreeButton/props.ts +95 -0
  47. package/src/TreeBrowser/v2/TreeButton/styles.ts +331 -0
  48. package/src/TreeBrowser/v2/TreeCollection/index.tsx +416 -0
  49. package/src/TreeBrowser/v2/TreeCollection/props.ts +88 -0
  50. package/src/TreeBrowser/v2/TreeCollection/styles.ts +157 -0
  51. package/src/TreeBrowser/v2/TreeNode/index.tsx +158 -0
  52. package/src/TreeBrowser/v2/TreeNode/props.ts +84 -0
  53. package/src/TreeBrowser/v2/index.tsx +438 -0
  54. package/src/TreeBrowser/v2/props.ts +245 -0
  55. package/src/TreeBrowser/v2/styles.ts +79 -0
  56. package/src/exports/a.ts +32 -0
  57. package/src/exports/b.ts +32 -0
  58. package/tsconfig.build.tsbuildinfo +1 -1
  59. package/types/TreeBrowser/v1/TreeBrowserContext.d.ts.map +1 -0
  60. package/types/TreeBrowser/v1/TreeButton/index.d.ts.map +1 -0
  61. package/types/TreeBrowser/v1/TreeButton/props.d.ts.map +1 -0
  62. package/types/TreeBrowser/v1/TreeButton/styles.d.ts.map +1 -0
  63. package/types/TreeBrowser/v1/TreeButton/theme.d.ts.map +1 -0
  64. package/types/TreeBrowser/{TreeCollection → v1/TreeCollection}/index.d.ts +1 -1
  65. package/types/TreeBrowser/v1/TreeCollection/index.d.ts.map +1 -0
  66. package/types/TreeBrowser/v1/TreeCollection/props.d.ts.map +1 -0
  67. package/types/TreeBrowser/v1/TreeCollection/styles.d.ts.map +1 -0
  68. package/types/TreeBrowser/v1/TreeCollection/theme.d.ts.map +1 -0
  69. package/types/TreeBrowser/v1/TreeNode/index.d.ts.map +1 -0
  70. package/types/TreeBrowser/v1/TreeNode/props.d.ts.map +1 -0
  71. package/types/TreeBrowser/v1/index.d.ts.map +1 -0
  72. package/types/TreeBrowser/v1/props.d.ts.map +1 -0
  73. package/types/TreeBrowser/v1/styles.d.ts.map +1 -0
  74. package/types/TreeBrowser/v1/theme.d.ts.map +1 -0
  75. package/types/TreeBrowser/v2/TreeBrowserContext.d.ts +24 -0
  76. package/types/TreeBrowser/v2/TreeBrowserContext.d.ts.map +1 -0
  77. package/types/TreeBrowser/v2/TreeButton/index.d.ts +44 -0
  78. package/types/TreeBrowser/v2/TreeButton/index.d.ts.map +1 -0
  79. package/types/TreeBrowser/v2/TreeButton/props.d.ts +37 -0
  80. package/types/TreeBrowser/v2/TreeButton/props.d.ts.map +1 -0
  81. package/types/TreeBrowser/v2/TreeButton/styles.d.ts +19 -0
  82. package/types/TreeBrowser/v2/TreeButton/styles.d.ts.map +1 -0
  83. package/types/TreeBrowser/v2/TreeCollection/index.d.ts +66 -0
  84. package/types/TreeBrowser/v2/TreeCollection/index.d.ts.map +1 -0
  85. package/types/TreeBrowser/v2/TreeCollection/props.d.ts +24 -0
  86. package/types/TreeBrowser/v2/TreeCollection/props.d.ts.map +1 -0
  87. package/types/TreeBrowser/v2/TreeCollection/styles.d.ts +18 -0
  88. package/types/TreeBrowser/v2/TreeCollection/styles.d.ts.map +1 -0
  89. package/types/TreeBrowser/v2/TreeNode/index.d.ts +41 -0
  90. package/types/TreeBrowser/v2/TreeNode/index.d.ts.map +1 -0
  91. package/types/TreeBrowser/v2/TreeNode/props.d.ts +25 -0
  92. package/types/TreeBrowser/v2/TreeNode/props.d.ts.map +1 -0
  93. package/types/TreeBrowser/v2/index.d.ts +70 -0
  94. package/types/TreeBrowser/v2/index.d.ts.map +1 -0
  95. package/types/TreeBrowser/v2/props.d.ts +163 -0
  96. package/types/TreeBrowser/v2/props.d.ts.map +1 -0
  97. package/types/TreeBrowser/v2/styles.d.ts +16 -0
  98. package/types/TreeBrowser/v2/styles.d.ts.map +1 -0
  99. package/types/exports/a.d.ts +9 -0
  100. package/types/exports/a.d.ts.map +1 -0
  101. package/types/exports/b.d.ts +9 -0
  102. package/types/exports/b.d.ts.map +1 -0
  103. package/src/index.ts +0 -32
  104. package/types/TreeBrowser/TreeBrowserContext.d.ts.map +0 -1
  105. package/types/TreeBrowser/TreeButton/index.d.ts.map +0 -1
  106. package/types/TreeBrowser/TreeButton/props.d.ts.map +0 -1
  107. package/types/TreeBrowser/TreeButton/styles.d.ts.map +0 -1
  108. package/types/TreeBrowser/TreeButton/theme.d.ts.map +0 -1
  109. package/types/TreeBrowser/TreeCollection/index.d.ts.map +0 -1
  110. package/types/TreeBrowser/TreeCollection/props.d.ts.map +0 -1
  111. package/types/TreeBrowser/TreeCollection/styles.d.ts.map +0 -1
  112. package/types/TreeBrowser/TreeCollection/theme.d.ts.map +0 -1
  113. package/types/TreeBrowser/TreeNode/index.d.ts.map +0 -1
  114. package/types/TreeBrowser/TreeNode/props.d.ts.map +0 -1
  115. package/types/TreeBrowser/index.d.ts.map +0 -1
  116. package/types/TreeBrowser/props.d.ts.map +0 -1
  117. package/types/TreeBrowser/styles.d.ts.map +0 -1
  118. package/types/TreeBrowser/theme.d.ts.map +0 -1
  119. package/types/index.d.ts +0 -9
  120. package/types/index.d.ts.map +0 -1
  121. /package/es/TreeBrowser/{TreeBrowserContext.js → v1/TreeBrowserContext.js} +0 -0
  122. /package/es/TreeBrowser/{TreeButton → v1/TreeButton}/props.js +0 -0
  123. /package/es/TreeBrowser/{TreeButton → v1/TreeButton}/styles.js +0 -0
  124. /package/es/TreeBrowser/{TreeButton → v1/TreeButton}/theme.js +0 -0
  125. /package/es/TreeBrowser/{TreeCollection → v1/TreeCollection}/props.js +0 -0
  126. /package/es/TreeBrowser/{TreeCollection → v1/TreeCollection}/styles.js +0 -0
  127. /package/es/TreeBrowser/{TreeCollection → v1/TreeCollection}/theme.js +0 -0
  128. /package/es/TreeBrowser/{TreeNode → v1/TreeNode}/props.js +0 -0
  129. /package/es/TreeBrowser/{props.js → v1/props.js} +0 -0
  130. /package/es/TreeBrowser/{styles.js → v1/styles.js} +0 -0
  131. /package/es/TreeBrowser/{theme.js → v1/theme.js} +0 -0
  132. /package/lib/TreeBrowser/{TreeBrowserContext.js → v1/TreeBrowserContext.js} +0 -0
  133. /package/lib/TreeBrowser/{TreeButton → v1/TreeButton}/props.js +0 -0
  134. /package/lib/TreeBrowser/{TreeButton → v1/TreeButton}/styles.js +0 -0
  135. /package/lib/TreeBrowser/{TreeButton → v1/TreeButton}/theme.js +0 -0
  136. /package/lib/TreeBrowser/{TreeCollection → v1/TreeCollection}/props.js +0 -0
  137. /package/lib/TreeBrowser/{TreeCollection → v1/TreeCollection}/styles.js +0 -0
  138. /package/lib/TreeBrowser/{TreeCollection → v1/TreeCollection}/theme.js +0 -0
  139. /package/lib/TreeBrowser/{TreeNode → v1/TreeNode}/props.js +0 -0
  140. /package/lib/TreeBrowser/{props.js → v1/props.js} +0 -0
  141. /package/lib/TreeBrowser/{styles.js → v1/styles.js} +0 -0
  142. /package/lib/TreeBrowser/{theme.js → v1/theme.js} +0 -0
  143. /package/src/TreeBrowser/{README.md → v1/README.md} +0 -0
  144. /package/src/TreeBrowser/{TreeBrowserContext.ts → v1/TreeBrowserContext.ts} +0 -0
  145. /package/src/TreeBrowser/{TreeButton → v1/TreeButton}/props.ts +0 -0
  146. /package/src/TreeBrowser/{TreeButton → v1/TreeButton}/styles.ts +0 -0
  147. /package/src/TreeBrowser/{TreeButton → v1/TreeButton}/theme.ts +0 -0
  148. /package/src/TreeBrowser/{TreeCollection → v1/TreeCollection}/props.ts +0 -0
  149. /package/src/TreeBrowser/{TreeCollection → v1/TreeCollection}/styles.ts +0 -0
  150. /package/src/TreeBrowser/{TreeCollection → v1/TreeCollection}/theme.ts +0 -0
  151. /package/src/TreeBrowser/{TreeNode → v1/TreeNode}/props.ts +0 -0
  152. /package/src/TreeBrowser/{props.ts → v1/props.ts} +0 -0
  153. /package/src/TreeBrowser/{styles.ts → v1/styles.ts} +0 -0
  154. /package/src/TreeBrowser/{theme.ts → v1/theme.ts} +0 -0
  155. /package/types/TreeBrowser/{TreeBrowserContext.d.ts → v1/TreeBrowserContext.d.ts} +0 -0
  156. /package/types/TreeBrowser/{TreeButton → v1/TreeButton}/index.d.ts +0 -0
  157. /package/types/TreeBrowser/{TreeButton → v1/TreeButton}/props.d.ts +0 -0
  158. /package/types/TreeBrowser/{TreeButton → v1/TreeButton}/styles.d.ts +0 -0
  159. /package/types/TreeBrowser/{TreeButton → v1/TreeButton}/theme.d.ts +0 -0
  160. /package/types/TreeBrowser/{TreeCollection → v1/TreeCollection}/props.d.ts +0 -0
  161. /package/types/TreeBrowser/{TreeCollection → v1/TreeCollection}/styles.d.ts +0 -0
  162. /package/types/TreeBrowser/{TreeCollection → v1/TreeCollection}/theme.d.ts +0 -0
  163. /package/types/TreeBrowser/{TreeNode → v1/TreeNode}/index.d.ts +0 -0
  164. /package/types/TreeBrowser/{TreeNode → v1/TreeNode}/props.d.ts +0 -0
  165. /package/types/TreeBrowser/{index.d.ts → v1/index.d.ts} +0 -0
  166. /package/types/TreeBrowser/{props.d.ts → v1/props.d.ts} +0 -0
  167. /package/types/TreeBrowser/{styles.d.ts → v1/styles.d.ts} +0 -0
  168. /package/types/TreeBrowser/{theme.d.ts → v1/theme.d.ts} +0 -0
@@ -0,0 +1,158 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2021 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import { Component, ContextType } from 'react'
26
+
27
+ import { Img } from '@instructure/ui-img/latest'
28
+ import { withStyle } from '@instructure/emotion'
29
+ import { renderIconWithProps } from '@instructure/ui-icons'
30
+
31
+ import generateStyles from '../TreeButton/styles'
32
+ import type { TreeBrowserNodeProps } from './props'
33
+ import { allowedProps } from './props'
34
+ import TreeBrowserContext from '../TreeBrowserContext'
35
+
36
+ // Map TreeBrowser size tokens to icon size tokens
37
+ const treeBrowserSizeToIconSize = {
38
+ small: 'md',
39
+ medium: 'lg',
40
+ large: 'xl'
41
+ } as const
42
+
43
+ // Todo: merge TreeButton and TreeNode: TreeButton should be a special type of TreeNode
44
+
45
+ /**
46
+ ---
47
+ parent: TreeBrowser
48
+ id: TreeBrowser.Node
49
+ ---
50
+ A helper class used to render the :renderBeforeItems and :renderAfterItems
51
+ in the TreeBrowser.
52
+ **/
53
+ @withStyle(generateStyles, 'TreeBrowserTreeButton')
54
+ class TreeNode extends Component<TreeBrowserNodeProps, { isHovered: boolean }> {
55
+ static readonly componentId = 'TreeBrowser.Node'
56
+
57
+ static allowedProps = allowedProps
58
+
59
+ static contextType = TreeBrowserContext
60
+ declare context: ContextType<typeof TreeBrowserContext>
61
+
62
+ static defaultProps = {
63
+ size: 'medium',
64
+ variant: 'folderTree',
65
+ selected: false,
66
+ focused: false,
67
+ hoverable: true
68
+ }
69
+
70
+ ref: Element | null = null
71
+
72
+ state = {
73
+ isHovered: false
74
+ }
75
+
76
+ componentDidMount() {
77
+ this.props.makeStyles?.({
78
+ animation: this.context?.animation,
79
+ hoverable: this.props.hoverable
80
+ })
81
+ }
82
+
83
+ componentDidUpdate() {
84
+ this.props.makeStyles?.({
85
+ animation: this.context?.animation,
86
+ hoverable: this.props.hoverable
87
+ })
88
+ }
89
+
90
+ handleRef = (el: HTMLDivElement) => {
91
+ if (el && typeof this.props.containerRef === 'function') {
92
+ this.props.containerRef(el.parentElement)
93
+ }
94
+ this.ref = el
95
+ }
96
+
97
+ handleMouseEnter = () => {
98
+ if (this.props.hoverable) {
99
+ this.setState({ isHovered: true })
100
+ }
101
+ }
102
+
103
+ handleMouseLeave = () => {
104
+ if (this.props.hoverable) {
105
+ this.setState({ isHovered: false })
106
+ }
107
+ }
108
+ getIconColor(): 'baseColor' | 'onColor' | 'inverseColor' {
109
+ const { selected } = this.props
110
+ const { isHovered } = this.state
111
+
112
+ // Priority: selected > hover > default
113
+ if (selected) return 'onColor'
114
+ if (isHovered) return 'inverseColor'
115
+ return 'baseColor'
116
+ }
117
+ renderItemImage() {
118
+ const { thumbnail, itemIcon, styles, size } = this.props
119
+
120
+ if (thumbnail) {
121
+ return (
122
+ <div css={styles?.thumbnail}>
123
+ <Img src={thumbnail} constrain="cover" alt="" />
124
+ </div>
125
+ )
126
+ }
127
+ if (itemIcon) {
128
+ const iconSize = treeBrowserSizeToIconSize[size!]
129
+ const iconColor = this.getIconColor()
130
+ const iconElement = renderIconWithProps(itemIcon, iconSize, iconColor)
131
+
132
+ return <div css={styles?.icon}>{iconElement}</div>
133
+ }
134
+ return undefined
135
+ }
136
+
137
+ render() {
138
+ const { children, styles } = this.props
139
+ return (
140
+ <div
141
+ ref={this.handleRef}
142
+ tabIndex={-1}
143
+ css={styles?.treeButton}
144
+ data-cid="TreeNode"
145
+ onMouseEnter={this.handleMouseEnter}
146
+ onMouseLeave={this.handleMouseLeave}
147
+ >
148
+ <span css={styles?.layout}>
149
+ {this.renderItemImage()}
150
+ <span css={styles?.node}>{children}</span>
151
+ </span>
152
+ </div>
153
+ )
154
+ }
155
+ }
156
+
157
+ export default TreeNode
158
+ export { TreeNode }
@@ -0,0 +1,84 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import React from 'react'
26
+ import type { TreeBrowserButtonTheme } from '@instructure/shared-types'
27
+ import type { WithStyleProps } from '@instructure/emotion'
28
+
29
+ import type {
30
+ TreeBrowserButtonProps,
31
+ TreeBrowserButtonStyle
32
+ } from '../TreeButton/props'
33
+ import { CollectionData } from '../props'
34
+
35
+ type TreeBrowserNodeOwnProps = Pick<
36
+ TreeBrowserButtonProps,
37
+ | 'id'
38
+ | 'size'
39
+ | 'variant'
40
+ | 'selected'
41
+ | 'focused'
42
+ | 'itemIcon'
43
+ | 'thumbnail'
44
+ | 'level'
45
+ | 'containerRef'
46
+ | 'onClick'
47
+ > & {
48
+ onKeyDown?: (e: React.KeyboardEvent, data: CollectionData) => void
49
+ /**
50
+ * The children to be rendered within the `<TreeNode />`
51
+ */
52
+ children?: React.ReactNode
53
+ /**
54
+ * Whether the node should show hover effects.
55
+ * Set to false to disable hover styling when the node contains custom interactive content.
56
+ * @default true
57
+ */
58
+ hoverable?: boolean
59
+ }
60
+
61
+ type PropKeys = keyof TreeBrowserNodeOwnProps
62
+
63
+ type AllowedPropKeys = Readonly<Array<PropKeys>>
64
+
65
+ type TreeBrowserNodeProps = TreeBrowserNodeOwnProps &
66
+ WithStyleProps<TreeBrowserButtonTheme, TreeBrowserButtonStyle>
67
+ const allowedProps: AllowedPropKeys = [
68
+ 'id',
69
+ 'size',
70
+ 'variant',
71
+ 'selected',
72
+ 'focused',
73
+ 'itemIcon',
74
+ 'thumbnail',
75
+ 'level',
76
+ 'children',
77
+ 'containerRef',
78
+ 'onKeyDown',
79
+ 'onClick',
80
+ 'hoverable'
81
+ ]
82
+
83
+ export type { TreeBrowserNodeProps }
84
+ export { allowedProps }
@@ -0,0 +1,438 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import { Component, SyntheticEvent } from 'react'
26
+ import keycode from 'keycode'
27
+
28
+ import {
29
+ FolderClosedInstUIIcon,
30
+ FileTextInstUIIcon
31
+ } from '@instructure/ui-icons'
32
+
33
+ import { omitProps, pickProps } from '@instructure/ui-react-utils'
34
+ import { withStyle } from '@instructure/emotion'
35
+
36
+ import { TreeCollection } from './TreeCollection'
37
+ import { TreeButton } from './TreeButton'
38
+ import { TreeNode } from './TreeNode'
39
+
40
+ import generateStyles from './styles'
41
+ import type {
42
+ Collection,
43
+ CollectionData,
44
+ CollectionProps,
45
+ TreeBrowserProps,
46
+ TreeBrowserState
47
+ } from './props'
48
+ import { allowedProps } from './props'
49
+ import TreeBrowserContext from './TreeBrowserContext'
50
+
51
+ /**
52
+ ---
53
+ category: components
54
+ ---
55
+ **/
56
+ @withStyle(generateStyles)
57
+ class TreeBrowser extends Component<TreeBrowserProps, TreeBrowserState> {
58
+ static readonly componentId = 'TreeBrowser'
59
+
60
+ static allowedProps = allowedProps
61
+
62
+ static defaultProps = {
63
+ size: 'medium',
64
+ variant: 'folderTree',
65
+ showRootCollection: true,
66
+ collectionIcon: FolderClosedInstUIIcon,
67
+ collectionIconExpanded: FolderClosedInstUIIcon,
68
+ itemIcon: FileTextInstUIIcon,
69
+ getItemProps: (props: unknown) => props,
70
+ getCollectionProps: (props: unknown) => props,
71
+ defaultExpanded: [],
72
+ selectionType: 'none',
73
+ sortOrder: function () {
74
+ return 0
75
+ },
76
+ animation: true
77
+ }
78
+
79
+ static Node = TreeNode
80
+ static Collection = TreeCollection
81
+ static Button = TreeButton
82
+
83
+ ref: Element | null = null
84
+
85
+ constructor(props: TreeBrowserProps) {
86
+ super(props)
87
+ const initialState: TreeBrowserState = {
88
+ selection: ''
89
+ }
90
+
91
+ // Handle expanded state (controlled vs uncontrolled)
92
+ if (typeof this.props.expanded === 'undefined') {
93
+ initialState.expanded = this.props.defaultExpanded
94
+ }
95
+
96
+ // Handle selection state (controlled vs uncontrolled)
97
+ if (typeof this.props.selection === 'undefined') {
98
+ initialState.selection = this.props.defaultSelection || ''
99
+ }
100
+
101
+ this.state = initialState
102
+ }
103
+
104
+ componentDidMount() {
105
+ this.props.makeStyles?.()
106
+ }
107
+
108
+ componentDidUpdate() {
109
+ this.props.makeStyles?.()
110
+ }
111
+
112
+ get _root() {
113
+ console.warn(
114
+ '_root property is deprecated and will be removed in v9, please use ref instead'
115
+ )
116
+ return this.ref
117
+ }
118
+
119
+ handleCollectionClick = (
120
+ e: SyntheticEvent,
121
+ collection: CollectionData,
122
+ expand = true
123
+ ) => {
124
+ e.stopPropagation()
125
+ const { onCollectionClick } = this.props
126
+
127
+ if (expand) this.expandOrCollapseNode(collection)
128
+ onCollectionClick?.(collection.id as any, collection) // TODO: this should pass the event as the first arg
129
+ this.handleSelection(collection.id, 'collection')
130
+ }
131
+
132
+ handleItemClick = (e: SyntheticEvent, item: CollectionData) => {
133
+ e.stopPropagation()
134
+ this.props.onItemClick?.(item)
135
+ this.handleSelection(item.id, 'item')
136
+ }
137
+
138
+ handleKeyDown = (event: React.KeyboardEvent, node?: CollectionData) => {
139
+ switch (event.keyCode) {
140
+ case keycode.codes.down:
141
+ case keycode.codes.j:
142
+ event.stopPropagation()
143
+ this.moveFocus(1)
144
+ break
145
+ case keycode.codes.up:
146
+ case keycode.codes.k:
147
+ event.stopPropagation()
148
+ this.moveFocus(-1)
149
+ break
150
+ case keycode.codes.home:
151
+ case keycode.codes.end:
152
+ event.stopPropagation()
153
+ this.homeOrEnd(event.keyCode)
154
+ break
155
+ case keycode.codes.left:
156
+ case keycode.codes.right:
157
+ event.stopPropagation()
158
+ this.handleLeftOrRightArrow(event.keyCode, node)
159
+ break
160
+ case keycode.codes.enter:
161
+ case keycode.codes.space:
162
+ event.stopPropagation()
163
+ this.handleActivation(event, node)
164
+ break
165
+ default:
166
+ return
167
+ }
168
+ event.preventDefault()
169
+ }
170
+
171
+ get collections() {
172
+ const { collections, rootId } = this.props
173
+ if (typeof rootId !== 'undefined') {
174
+ return [collections[rootId]]
175
+ } else {
176
+ return Object.keys(collections)
177
+ .map((id) => collections[id])
178
+ .filter((collection) => collection != null)
179
+ }
180
+ }
181
+
182
+ get expanded() {
183
+ return this.getExpanded(this.state, this.props)
184
+ }
185
+
186
+ getExpanded(state: TreeBrowserState, props: TreeBrowserProps) {
187
+ return typeof props.expanded === 'undefined'
188
+ ? state.expanded!
189
+ : props.expanded
190
+ }
191
+
192
+ get selection() {
193
+ return this.getSelection(this.state, this.props)
194
+ }
195
+
196
+ getSelection(state: TreeBrowserState, props: TreeBrowserProps) {
197
+ return typeof props.selection === 'undefined'
198
+ ? state.selection
199
+ : props.selection
200
+ }
201
+
202
+ expandOrCollapseNode(collection: CollectionData) {
203
+ this.props.onCollectionToggle?.(collection)
204
+ if (typeof this.props.expanded === 'undefined') {
205
+ this.setState((state, props) => {
206
+ const expanded = [...this.getExpanded(state, props)]
207
+ const expandedIndex = this.getExpandedIndex(expanded, collection.id)
208
+ if (collection.expanded && expandedIndex < 0) {
209
+ expanded.push(collection.id)
210
+ } else if (expandedIndex >= 0) {
211
+ expanded.splice(expandedIndex, 1)
212
+ }
213
+ return { expanded }
214
+ })
215
+ }
216
+ }
217
+
218
+ handleSelection(id: string | number | undefined, type: string) {
219
+ const { selectionType, onSelectionChange } = this.props
220
+
221
+ if (selectionType !== 'single') return
222
+
223
+ const selection = `${type}_${id}`
224
+
225
+ // Call the callback if provided (for controlled selection)
226
+ if (onSelectionChange) {
227
+ onSelectionChange(selection, type as 'item' | 'collection', id)
228
+ }
229
+
230
+ // Update internal state only if uncontrolled
231
+ if (typeof this.props.selection === 'undefined') {
232
+ this.setState((state) => {
233
+ if (state.selection !== selection) {
234
+ return { selection }
235
+ } else {
236
+ return state
237
+ }
238
+ })
239
+ }
240
+ }
241
+
242
+ clearSelection = () => {
243
+ const { onSelectionChange } = this.props
244
+
245
+ // Call the callback if provided (for controlled selection)
246
+ if (onSelectionChange) {
247
+ onSelectionChange('', 'item', undefined)
248
+ }
249
+
250
+ // Update internal state only if uncontrolled
251
+ if (typeof this.props.selection === 'undefined') {
252
+ this.setState({ selection: '' })
253
+ }
254
+ }
255
+
256
+ getNavigableNodes() {
257
+ return Array.from(this.ref!.querySelectorAll('[role="treeitem"]'))
258
+ }
259
+
260
+ moveFocus(delta: number) {
261
+ const nodes = this.getNavigableNodes()
262
+ const closest = window.document.activeElement!.closest('[role="treeitem"]')!
263
+ const active = nodes.indexOf(closest)
264
+ let next = active + delta
265
+ if (next < 0) {
266
+ next = 0
267
+ } else if (next >= nodes.length) {
268
+ next = nodes.length - 1
269
+ }
270
+ nodes.forEach((n) => {
271
+ n.setAttribute('tabindex', '-1')
272
+ })
273
+ nodes[next].setAttribute('tabindex', '0')
274
+ ;(nodes[next] as any).focus()
275
+ }
276
+
277
+ homeOrEnd(keyCode: number) {
278
+ const length = this.getNavigableNodes().length
279
+ if (keyCode === keycode.codes.home) {
280
+ this.moveFocus(1 - length)
281
+ } else {
282
+ this.moveFocus(length - 1)
283
+ }
284
+ }
285
+
286
+ handleLeftOrRightArrow(keyCode: number, node?: CollectionData) {
287
+ const ltr = !(
288
+ this.ref!.parentElement!.dir === 'rtl' || document.dir === 'rtl'
289
+ )
290
+ if (
291
+ (ltr && keyCode === keycode.codes.left) ||
292
+ (!ltr && keyCode == keycode.codes.right)
293
+ ) {
294
+ this.handleCloseOrPrevious(node)
295
+ } else {
296
+ this.handleOpenOrNext(node)
297
+ }
298
+ }
299
+
300
+ handleOpenOrNext(node?: CollectionData) {
301
+ if (
302
+ node &&
303
+ !this.expanded.includes(node.id) &&
304
+ node.type === 'collection'
305
+ ) {
306
+ this.expandOrCollapseNode(node)
307
+ } else {
308
+ this.moveFocus(1)
309
+ }
310
+ }
311
+
312
+ handleCloseOrPrevious(node?: CollectionData) {
313
+ if (node && this.expanded.includes(node.id) && node.type === 'collection') {
314
+ this.expandOrCollapseNode(node)
315
+ } else {
316
+ this.moveFocus(-1)
317
+ }
318
+ }
319
+
320
+ handleActivation(event: React.KeyboardEvent, node?: CollectionData) {
321
+ if (node == null) return
322
+ if (node.type === 'collection') {
323
+ this.handleCollectionClick(
324
+ event,
325
+ node,
326
+ this.props.selectionType === 'none'
327
+ )
328
+ } else {
329
+ this.handleItemClick(event, node)
330
+ }
331
+ }
332
+
333
+ getSubCollections(collection: Collection) {
334
+ const collections = collection.collections
335
+ ? [...collection.collections]
336
+ : []
337
+ return collections
338
+ .map((id) => this.getCollectionProps(this.props.collections[id]))
339
+ .filter((collection) => collection != null)
340
+ .sort(this.props.sortOrder)
341
+ }
342
+
343
+ getItems(collection: Collection) {
344
+ if (collection.items) {
345
+ const items = collection.items ? [...collection.items] : []
346
+ return items
347
+ .map((id) => {
348
+ return { ...this.props.items[id] }
349
+ })
350
+ .filter((item) => item != null)
351
+ .sort(this.props.sortOrder)
352
+ } else {
353
+ return []
354
+ }
355
+ }
356
+
357
+ getIsFlattened = (collection: Collection) => {
358
+ const { rootId, showRootCollection } = this.props
359
+ return (
360
+ !showRootCollection &&
361
+ typeof rootId !== 'undefined' &&
362
+ collection.id === rootId
363
+ )
364
+ }
365
+
366
+ getCollectionProps(collection: Collection): CollectionProps {
367
+ return {
368
+ id: collection.id,
369
+ name: collection.name,
370
+ collections: this.getSubCollections(collection),
371
+ items: this.getItems(collection),
372
+ descriptor: collection.descriptor,
373
+ containerRef: collection.containerRef,
374
+ renderBeforeItems: collection.renderBeforeItems,
375
+ renderAfterItems: collection.renderAfterItems,
376
+ expanded: this.getExpandedIndex(this.expanded, collection.id) >= 0,
377
+ isCollectionFlattened: this.getIsFlattened(collection),
378
+ compareFunc: collection.compareFunc
379
+ }
380
+ }
381
+
382
+ getExpandedIndex(
383
+ expanded: (string | number | undefined)[],
384
+ id?: string | number
385
+ ) {
386
+ return expanded.findIndex((expanded) => String(expanded) === String(id))
387
+ }
388
+
389
+ renderRoot() {
390
+ return this.collections
391
+ .sort(this.props.sortOrder)
392
+ .map((collection, i) => (
393
+ <TreeCollection
394
+ key={i}
395
+ {...pickProps(omitProps(this.props), TreeCollection.allowedProps)}
396
+ {...this.getCollectionProps(collection)}
397
+ selection={this.selection}
398
+ onItemClick={this.handleItemClick}
399
+ onCollectionClick={this.handleCollectionClick}
400
+ onKeyDown={this.handleKeyDown}
401
+ numChildren={this.collections.length}
402
+ level={1}
403
+ position={1}
404
+ renderContent={this.props.renderContent}
405
+ />
406
+ ))
407
+ }
408
+
409
+ render() {
410
+ const { styles } = this.props
411
+
412
+ return (
413
+ <TreeBrowserContext.Provider
414
+ value={{
415
+ animation: this.props.animation,
416
+ clearSelection: this.clearSelection
417
+ }}
418
+ >
419
+ <ul
420
+ css={styles?.treeBrowser}
421
+ tabIndex={0}
422
+ role="tree"
423
+ onKeyDown={this.handleKeyDown}
424
+ ref={(el) => {
425
+ this.ref = el
426
+ }}
427
+ aria-label={this.props.treeLabel}
428
+ data-cid="TreeBrowser"
429
+ >
430
+ {this.renderRoot()}
431
+ </ul>
432
+ </TreeBrowserContext.Provider>
433
+ )
434
+ }
435
+ }
436
+
437
+ export default TreeBrowser
438
+ export { TreeBrowser }