-
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(design): add tree component (#1622)
Co-authored-by: Elain Tsai <xelaint@gmail.com>
- Loading branch information
1 parent
a61dc02
commit f211e48
Showing
46 changed files
with
1,663 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Tree | ||
|
||
Trees are used to visualize hierarchial information. They are often used to display navigational structures like nested lists of links. | ||
|
||
## Overview | ||
|
||
The `DaffTreeComponent` renders a tree structure. Typically, this is a structure of `<a>` and `<button>` elements that allow users to either navigate to a page, or explore the tree to find an item inside the tree that they want to navigate to. | ||
|
||
Instead of defining a recursive tree structure of components, which is often prohibitively slow when rendering large trees, the `DaffTreeComponent` renders a flattened tree, using padding to indicate the nesting level of the tree elements. | ||
|
||
Generally, tree usage consists of taking existing tree data, converting it to the `DaffTreeData` format, setting the `tree` input on the `DaffTreeComponent`, and providing templates for the cases where the tree element has children or not. | ||
|
||
## Features | ||
|
||
The `DaffTreeComponent` controls the rendering of the structure of the tree and provides template slots so that you can control the ultimate UI rendered for each node. | ||
|
||
Currently, we support two kind of templates `daffTreeItemWithChildrenTpl` and `daffTreeItemTpl`. These templates allow you to control the content of each tree node. In the case of `daffTreeItemWithChildrenTpl` a `click` handler will be automatically applied (along with an icon indicating the expanded state) to the template to allow users to automatically open and close the node. | ||
|
||
```html | ||
<ng-template #daffTreeItemWithChildrenTpl let-node> | ||
<button daffTreeItem [node]="node">{{ node.title }} </button> | ||
</ng-template> | ||
|
||
<ng-template #daffTreeItemTpl let-node> | ||
<a daffTreeItem [node]="node" [routerLink]="node.url">{{ node.title }}</a> | ||
</ng-template> | ||
``` | ||
|
||
## Usage | ||
|
||
### Basic Tree | ||
|
||
<design-land-example-viewer-container example="basic-tree"> | ||
</design-land-example-viewer-container> | ||
|
||
## Accessibility | ||
|
||
The `DaffTreeComponent` follows the specification for a [disclosure navigation menu](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/) instead of a [tree view](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json", | ||
"dest": "../../dist/design/examples", | ||
"deleteDestPath": false, | ||
"lib": { | ||
"entryFile": "src/index.ts", | ||
"styleIncludePaths": ["../../src/scss"] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"name": "@daffodil/design/tree/examples" | ||
} |
10 changes: 10 additions & 0 deletions
10
libs/design/tree/examples/src/basic-tree/basic-tree.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<ul daff-tree [tree]="tree"> | ||
<ng-template #daffTreeItemWithChildrenTpl let-node> | ||
<button daffTreeItem [node]="node">{{ node.title }} </button> | ||
</ng-template> | ||
|
||
<ng-template #daffTreeItemTpl let-node> | ||
<a daffTreeItem [node]="node" [routerLink]="node.url">{{ node.title }}</a> | ||
</ng-template> | ||
</ul> | ||
|
39 changes: 39 additions & 0 deletions
39
libs/design/tree/examples/src/basic-tree/basic-tree.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { | ||
ChangeDetectionStrategy, | ||
Component, | ||
} from '@angular/core'; | ||
|
||
import { DaffTreeData } from '@daffodil/design/tree'; | ||
|
||
@Component({ | ||
// eslint-disable-next-line @angular-eslint/component-selector | ||
selector: 'basic-tree', | ||
templateUrl: './basic-tree.component.html', | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
}) | ||
export class BasicTreeComponent { | ||
tree: DaffTreeData<unknown> = { | ||
title: 'Root', | ||
items: [ | ||
{ | ||
title: 'Example Children', | ||
items: [ | ||
{ title: 'Example Child', url: '#', id: '', items: [], data: {}}, | ||
], | ||
url: '#', | ||
id: '', | ||
data: {}, | ||
}, | ||
{ | ||
title: 'Example Link', | ||
items: [], | ||
url: '#', | ||
id: '', | ||
data: {}, | ||
}, | ||
], | ||
url: '', | ||
id: '', | ||
data: {}, | ||
}; | ||
} |
22 changes: 22 additions & 0 deletions
22
libs/design/tree/examples/src/basic-tree/basic-tree.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { NgModule } from '@angular/core'; | ||
import { RouterModule } from '@angular/router'; | ||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; | ||
|
||
import { DaffTreeModule } from '@daffodil/design/tree'; | ||
|
||
import { BasicTreeComponent } from './basic-tree.component'; | ||
|
||
@NgModule({ | ||
declarations: [ | ||
BasicTreeComponent, | ||
], | ||
exports: [ | ||
BasicTreeComponent, | ||
], | ||
imports: [ | ||
RouterModule, | ||
DaffTreeModule, | ||
FontAwesomeModule, | ||
], | ||
}) | ||
export class BasicTreeModule { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './public_api'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { BasicTreeComponent } from './basic-tree/basic-tree.component'; | ||
export { BasicTreeModule } from './basic-tree/basic-tree.module'; | ||
export { BasicTreeComponent }; | ||
export const TREE_EXAMPLES = [ | ||
BasicTreeComponent, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", | ||
"dest": "../../dist/design/tree", | ||
"deleteDestPath": false, | ||
"lib": { | ||
"entryFile": "src/index.ts", | ||
"styleIncludePaths": ["../src/scss"] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"name": "@daffodil/design/tree" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './public_api'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export type RecursiveTreeKeyOfType<T> = keyof { | ||
[P in keyof T as T[P] extends T[]? P: never]: T[] | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/** | ||
* A basic tree type supporting supplemental data on a tree node. | ||
* | ||
* Tree elements are often slightly more than just basic titles and child items. | ||
* There may be other important data that needs to be available at render time. | ||
*/ | ||
export interface DaffTreeData<T> { | ||
title: string; | ||
url: string; | ||
id: string; | ||
items: DaffTreeData<T>[]; | ||
data: T; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { DaffTreeData } from './tree-data'; | ||
|
||
/** | ||
* A DaffTreeUi is the internal data structure used during tree rendering. | ||
* | ||
* This is an internal implementation detail type that. | ||
*/ | ||
export interface DaffTreeUi<T> extends DaffTreeData<T> { | ||
open: boolean; | ||
items: DaffTreeUi<T>[]; | ||
parent: DaffTreeUi<T>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export { DaffTreeModule } from './tree.module'; | ||
export { DaffTreeComponent } from './tree/tree.component'; | ||
export { DaffTreeItemDirective } from './tree-item/tree-item.directive'; | ||
export { DaffTreeData } from './interfaces/tree-data'; | ||
export { DaffTreeUi } from './interfaces/tree-ui'; | ||
export { daffTransformTreeInPlace } from './utils/transform-in-place'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { DaffTreeItemDirective } from './tree-item.directive'; | ||
|
||
describe('DaffTreeItemDirective', () => { | ||
it('should create an instance', () => { | ||
// const directive = new DaffTreeItemDirective(); | ||
// expect(directive).toBeTruthy(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { DOCUMENT } from '@angular/common'; | ||
import { | ||
Directive, | ||
HostBinding, | ||
HostListener, | ||
Inject, | ||
Input, | ||
} from '@angular/core'; | ||
|
||
import { DaffTreeNotifierService } from '../tree/tree-notifier.service'; | ||
import { DaffTreeFlatNode } from '../utils/flatten-tree'; | ||
|
||
/** | ||
* The `DaffTreeItemDirective` allows you to demarcate the elements which are | ||
* tree-children that interact with the parent tree. | ||
* | ||
* They can be used like: | ||
* | ||
* ```html | ||
* <ul daff-tree [tree]="tree"> | ||
* <ng-template #daffTreeItemWithChildrenTpl let-node> | ||
* <button daffTreeItem [node]="node">{{ node.title }} </button> | ||
* </ng-template> | ||
* | ||
* <ng-template #daffTreeItemTpl let-node> | ||
* <a daffTreeItem [node]="node" [routerLink]="node.url">{{ node.title }}</a> | ||
* </ng-template> | ||
* </ul> | ||
* ``` | ||
* | ||
* where `tree` is a {@link DaffTreeData} and `daff-tree` is a {@link DaffTreeComponent}. | ||
* | ||
*/ | ||
@Directive({ | ||
selector: '[daffTreeItem]', | ||
}) | ||
export class DaffTreeItemDirective { | ||
|
||
/** | ||
* The css class of the daff-tree. | ||
* | ||
* @docs-private | ||
*/ | ||
@HostBinding('class.daff-tree-item') class = true; | ||
|
||
/** | ||
* The css class of a DaffTreeItemDirective that has children. | ||
* | ||
* @docs-private | ||
*/ | ||
@HostBinding('class.daff-tree-item__parent') classParent = false; | ||
|
||
/** | ||
* The html `id` of the tree item. This is derived from the {@link DaffTreeData}. | ||
* | ||
* @docs-private | ||
*/ | ||
@HostBinding('attr.id') id; | ||
|
||
/** | ||
* Accessibility property, notifying users about whether | ||
* or not the tree item is open. | ||
* | ||
* @docs-private | ||
*/ | ||
@HostBinding('attr.aria-expanded') ariaExpanded: string; | ||
|
||
/** | ||
* A css variable indicating the depth of the tree. | ||
* You can use this to style your templates if you want to | ||
* use different designs at different depths. | ||
*/ | ||
@HostBinding('style.--depth') depth: number; | ||
|
||
/** | ||
* The CSS class indicating whether or not the tree is `selected`. | ||
*/ | ||
@HostBinding('class.selected') get selectedClass() { | ||
return this.selected; | ||
}; | ||
|
||
/** | ||
* The CSS class indicating whether or not the tree is `open`. | ||
*/ | ||
@HostBinding('class.open') openClass = false; | ||
|
||
/** | ||
* The {@link DaffTreeFlatNode} associated with this specific tree item. | ||
* | ||
* @docs-private | ||
*/ | ||
private _node: DaffTreeFlatNode; | ||
|
||
/** | ||
* The {@link DaffTreeFlatNode} associated with this specific tree item. | ||
*/ | ||
@Input() | ||
get node() { | ||
return this._node; | ||
}; | ||
set node(val: DaffTreeFlatNode) { | ||
this._node = val; | ||
this.id = 'tree-' + this._node.id; | ||
this.ariaExpanded = this._node._treeRef.open ? 'true' : 'false'; | ||
this.depth = this._node.level; | ||
this.classParent = this._node.hasChildren; | ||
this.openClass = this._node._treeRef.open; | ||
} | ||
|
||
/** | ||
* Whether or not the tree item is the currently active item. | ||
* Note that there is no requirement there there only be one active item at a time. | ||
*/ | ||
@Input() selected = false; | ||
|
||
constructor( | ||
@Inject(DOCUMENT) private document: any, | ||
private treeNotifier: DaffTreeNotifierService, | ||
) {} | ||
|
||
/** | ||
* @docs-private | ||
*/ | ||
@HostListener('keydown.escape') | ||
onEscape() { | ||
this.toggleParent(this.node); | ||
} | ||
|
||
/** | ||
* @docs-private | ||
*/ | ||
@HostListener('click') | ||
onClick() { | ||
if(this.node.hasChildren) { | ||
this.toggleTree(this.node); | ||
} | ||
this.treeNotifier.notify(); | ||
} | ||
|
||
/** | ||
* Toggle the open state of the tree's parent. | ||
*/ | ||
toggleParent(node: DaffTreeFlatNode) { | ||
if(node._treeRef?.parent.parent === undefined) { | ||
return; | ||
} | ||
node._treeRef.parent.open = !node._treeRef.parent.open; | ||
(<Document>this.document).getElementById('tree-' + node._treeRef.parent.id).focus(); | ||
} | ||
|
||
/** | ||
* Toggle the open state of this specific subtree tree. | ||
*/ | ||
toggleTree(node: DaffTreeFlatNode) { | ||
if(node._treeRef.open === false) { | ||
node._treeRef.open = true; | ||
} else { | ||
node._treeRef.open = false; | ||
} | ||
} | ||
} |
Oops, something went wrong.