github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/stories/components/table.stories.js (about) 1 import hbs from 'htmlbars-inline-precompile'; 2 import productMetadata from '../../app/utils/styleguide/product-metadata'; 3 4 import EmberObject, { computed } from '@ember/object'; 5 6 import { getOwner } from '@ember/application'; 7 import { on } from '@ember/object/evented'; 8 import Controller from '@ember/controller'; 9 10 export default { 11 title: 'Components/Table', 12 }; 13 14 /** 15 * The Ember integration for Storybook renders a container component with no routing, 16 * which means things that need query parameters, like sorting and pagination, won’t work. 17 18 * This initialiser turns on routing and accepts a controller definition that gets wired up 19 * to a generated `storybook` route. The controller is attached to the Storybook component 20 * as the `controller` property so its query parameters are accessible from the template. 21 */ 22 function injectRoutedController(controllerClass) { 23 return on('init', function () { 24 let container = getOwner(this); 25 container.register('controller:storybook', controllerClass); 26 27 let routerFactory = container.factoryFor('router:main'); 28 routerFactory.class.map(function () { 29 this.route('storybook'); 30 }); 31 32 /* eslint-disable-next-line ember/no-private-routing-service */ 33 let router = container.lookup('router:main'); 34 router.initialURL = 'storybook'; 35 router.startRouting(true); 36 37 this.set('controller', container.lookup('controller:storybook')); 38 }); 39 } 40 41 let longList = [ 42 { 43 city: 'New York', 44 growth: 0.048, 45 population: '8405837', 46 rank: '1', 47 state: 'New York', 48 }, 49 { 50 city: 'Los Angeles', 51 growth: 0.048, 52 population: '3884307', 53 rank: '2', 54 state: 'California', 55 }, 56 { 57 city: 'Chicago', 58 growth: -0.061, 59 population: '2718782', 60 rank: '3', 61 state: 'Illinois', 62 }, 63 { 64 city: 'Houston', 65 growth: 0.11, 66 population: '2195914', 67 rank: '4', 68 state: 'Texas', 69 }, 70 { 71 city: 'Philadelphia', 72 growth: 0.026, 73 population: '1553165', 74 rank: '5', 75 state: 'Pennsylvania', 76 }, 77 { 78 city: 'Phoenix', 79 growth: 0.14, 80 population: '1513367', 81 rank: '6', 82 state: 'Arizona', 83 }, 84 { 85 city: 'San Antonio', 86 growth: 0.21, 87 population: '1409019', 88 rank: '7', 89 state: 'Texas', 90 }, 91 { 92 city: 'San Diego', 93 growth: 0.105, 94 population: '1355896', 95 rank: '8', 96 state: 'California', 97 }, 98 { 99 city: 'Dallas', 100 growth: 0.056, 101 population: '1257676', 102 rank: '9', 103 state: 'Texas', 104 }, 105 { 106 city: 'San Jose', 107 growth: 0.105, 108 population: '998537', 109 rank: '10', 110 state: 'California', 111 }, 112 { 113 city: 'Austin', 114 growth: 0.317, 115 population: '885400', 116 rank: '11', 117 state: 'Texas', 118 }, 119 { 120 city: 'Indianapolis', 121 growth: 0.078, 122 population: '843393', 123 rank: '12', 124 state: 'Indiana', 125 }, 126 { 127 city: 'Jacksonville', 128 growth: 0.143, 129 population: '842583', 130 rank: '13', 131 state: 'Florida', 132 }, 133 { 134 city: 'San Francisco', 135 growth: 0.077, 136 population: '837442', 137 rank: '14', 138 state: 'California', 139 }, 140 { 141 city: 'Columbus', 142 growth: 0.148, 143 population: '822553', 144 rank: '15', 145 state: 'Ohio', 146 }, 147 { 148 city: 'Charlotte', 149 growth: 0.391, 150 population: '792862', 151 rank: '16', 152 state: 'North Carolina', 153 }, 154 { 155 city: 'Fort Worth', 156 growth: 0.451, 157 population: '792727', 158 rank: '17', 159 state: 'Texas', 160 }, 161 { 162 city: 'Detroit', 163 growth: -0.271, 164 population: '688701', 165 rank: '18', 166 state: 'Michigan', 167 }, 168 { 169 city: 'El Paso', 170 growth: 0.194, 171 population: '674433', 172 rank: '19', 173 state: 'Texas', 174 }, 175 { 176 city: 'Memphis', 177 growth: -0.053, 178 population: '653450', 179 rank: '20', 180 state: 'Tennessee', 181 }, 182 { 183 city: 'Seattle', 184 growth: 0.156, 185 population: '652405', 186 rank: '21', 187 state: 'Washington', 188 }, 189 { 190 city: 'Denver', 191 growth: 0.167, 192 population: '649495', 193 rank: '22', 194 state: 'Colorado', 195 }, 196 { 197 city: 'Washington', 198 growth: 0.13, 199 population: '646449', 200 rank: '23', 201 state: 'District of Columbia', 202 }, 203 { 204 city: 'Boston', 205 growth: 0.094, 206 population: '645966', 207 rank: '24', 208 state: 'Massachusetts', 209 }, 210 { 211 city: 'Nashville-Davidson', 212 growth: 0.162, 213 population: '634464', 214 rank: '25', 215 state: 'Tennessee', 216 }, 217 { 218 city: 'Baltimore', 219 growth: -0.04, 220 population: '622104', 221 rank: '26', 222 state: 'Maryland', 223 }, 224 { 225 city: 'Oklahoma City', 226 growth: 0.202, 227 population: '610613', 228 rank: '27', 229 state: 'Oklahoma', 230 }, 231 { 232 city: 'Louisville/Jefferson County', 233 growth: 0.1, 234 population: '609893', 235 rank: '28', 236 state: 'Kentucky', 237 }, 238 { 239 city: 'Portland', 240 growth: 0.15, 241 population: '609456', 242 rank: '29', 243 state: 'Oregon', 244 }, 245 { 246 city: 'Las Vegas', 247 growth: 0.245, 248 population: '603488', 249 rank: '30', 250 state: 'Nevada', 251 }, 252 { 253 city: 'Milwaukee', 254 growth: 0.003, 255 population: '599164', 256 rank: '31', 257 state: 'Wisconsin', 258 }, 259 { 260 city: 'Albuquerque', 261 growth: 0.235, 262 population: '556495', 263 rank: '32', 264 state: 'New Mexico', 265 }, 266 { 267 city: 'Tucson', 268 growth: 0.075, 269 population: '526116', 270 rank: '33', 271 state: 'Arizona', 272 }, 273 { 274 city: 'Fresno', 275 growth: 0.183, 276 population: '509924', 277 rank: '34', 278 state: 'California', 279 }, 280 { 281 city: 'Sacramento', 282 growth: 0.172, 283 population: '479686', 284 rank: '35', 285 state: 'California', 286 }, 287 { 288 city: 'Long Beach', 289 growth: 0.015, 290 population: '469428', 291 rank: '36', 292 state: 'California', 293 }, 294 { 295 city: 'Kansas City', 296 growth: 0.055, 297 population: '467007', 298 rank: '37', 299 state: 'Missouri', 300 }, 301 { 302 city: 'Mesa', 303 growth: 0.135, 304 population: '457587', 305 rank: '38', 306 state: 'Arizona', 307 }, 308 { 309 city: 'Virginia Beach', 310 growth: 0.051, 311 population: '448479', 312 rank: '39', 313 state: 'Virginia', 314 }, 315 { 316 city: 'Atlanta', 317 growth: 0.062, 318 population: '447841', 319 rank: '40', 320 state: 'Georgia', 321 }, 322 { 323 city: 'Colorado Springs', 324 growth: 0.214, 325 population: '439886', 326 rank: '41', 327 state: 'Colorado', 328 }, 329 { 330 city: 'Omaha', 331 growth: 0.059, 332 population: '434353', 333 rank: '42', 334 state: 'Nebraska', 335 }, 336 { 337 city: 'Raleigh', 338 growth: 0.487, 339 population: '431746', 340 rank: '43', 341 state: 'North Carolina', 342 }, 343 { 344 city: 'Miami', 345 growth: 0.149, 346 population: '417650', 347 rank: '44', 348 state: 'Florida', 349 }, 350 { 351 city: 'Oakland', 352 growth: 0.013, 353 population: '406253', 354 rank: '45', 355 state: 'California', 356 }, 357 { 358 city: 'Minneapolis', 359 growth: 0.045, 360 population: '400070', 361 rank: '46', 362 state: 'Minnesota', 363 }, 364 { 365 city: 'Tulsa', 366 growth: 0.013, 367 population: '398121', 368 rank: '47', 369 state: 'Oklahoma', 370 }, 371 { 372 city: 'Cleveland', 373 growth: -0.181, 374 population: '390113', 375 rank: '48', 376 state: 'Ohio', 377 }, 378 { 379 city: 'Wichita', 380 growth: 0.097, 381 population: '386552', 382 rank: '49', 383 state: 'Kansas', 384 }, 385 { 386 city: 'Arlington', 387 growth: 0.133, 388 population: '379577', 389 rank: '50', 390 state: 'Texas', 391 }, 392 ]; 393 394 export let Standard = () => { 395 return { 396 template: hbs` 397 <h5 class="title is-5">Table</h5> 398 <ListTable @source={{shortList}} as |t|> 399 <t.head> 400 <th>Name</th> 401 <th>Language</th> 402 <th>Description</th> 403 </t.head> 404 <t.body @key="model.name" as |row|> 405 <tr> 406 <td>{{row.model.name}}</td> 407 <td>{{row.model.lang}}</td> 408 <td>{{row.model.desc}}</td> 409 </tr> 410 </t.body> 411 </ListTable> 412 <p class="annotation">Tables have airy designs with a minimal amount of borders. This maximizes their utility.</p> 413 `, 414 context: { 415 shortList: productMetadata, 416 }, 417 }; 418 }; 419 420 export let Search = () => { 421 return { 422 template: hbs` 423 <h5 class="title is-5">Table search</h5> 424 <div class="boxed-section"> 425 <div class="boxed-section-head"> 426 Table Name 427 <SearchBox 428 @searchTerm={{mut controller.searchTerm}} 429 @placeholder="Search..." 430 @class="is-inline pull-right" 431 @inputClass="is-compact" /> 432 </div> 433 <div class="boxed-section-body {{if controller.filteredShortList.length "is-full-bleed"}}"> 434 {{#if controller.filteredShortList.length}} 435 <ListTable @source={{controller.filteredShortList}} as |t|> 436 <t.head> 437 <th>Name</th> 438 <th>Language</th> 439 <th>Description</th> 440 </t.head> 441 <t.body @key="model.name" as |row|> 442 <tr> 443 <td>{{row.model.name}}</td> 444 <td>{{row.model.lang}}</td> 445 <td>{{row.model.desc}}</td> 446 </tr> 447 </t.body> 448 </ListTable> 449 {{else}} 450 <div class="empty-message"> 451 <h3 class="empty-message-headline">No Matches</h3> 452 <p class="empty-message-body">No products match your query.</p> 453 </div> 454 {{/if}} 455 </div> 456 </div> 457 <p class="annotation">Tables compose with boxed-section and boxed-section composes with search box.</p> 458 `, 459 context: { 460 controller: EmberObject.extend({ 461 searchTerm: '', 462 463 filteredShortList: computed('searchTerm', function () { 464 let term = this.searchTerm.toLowerCase(); 465 return productMetadata.filter((product) => 466 product.name.toLowerCase().includes(term) 467 ); 468 }), 469 }).create(), 470 }, 471 }; 472 }; 473 474 export let SortableColumns = () => { 475 return { 476 template: hbs` 477 <h5 class="title is-5">Table with sortable columns</h5> 478 <ListTable @source={{sortedShortList}} @sortProperty={{controller.sortProperty}} @sortDescending={{controller.sortDescending}} as |t|> 479 <t.head> 480 <t.sort-by @prop="name">Name</t.sort-by> 481 <t.sort-by @prop="lang" @class="is-2">Language</t.sort-by> 482 <th>Description</th> 483 </t.head> 484 <t.body @key="model.name" as |row|> 485 <tr> 486 <td>{{row.model.name}}</td> 487 <td>{{row.model.lang}}</td> 488 <td>{{row.model.desc}}</td> 489 </tr> 490 </t.body> 491 </ListTable> 492 <p class="annotation">The list-table component provides a <code>sort-by</code> contextual component for building <code>link-to</code> components with the appropriate query params.</p> 493 <p class="annotation">This leaves the component stateless, relying on data to be passed down and sending actions back up via the router (via link-to).</p> 494 `, 495 context: { 496 injectRoutedController: injectRoutedController( 497 Controller.extend({ 498 queryParams: ['sortProperty', 'sortDescending'], 499 sortProperty: 'name', 500 sortDescending: false, 501 }) 502 ), 503 504 sortedShortList: computed( 505 'controller.{sortProperty,sortDescending}', 506 function () { 507 let sorted = productMetadata.sortBy( 508 this.get('controller.sortProperty') || 'name' 509 ); 510 return this.get('controller.sortDescending') 511 ? sorted.reverse() 512 : sorted; 513 } 514 ), 515 }, 516 }; 517 }; 518 519 export let MultiRow = () => { 520 return { 521 template: hbs` 522 <h5 class="title is-5">Multi-row Table</h5> 523 <ListTable @source={{sortedShortList}} @sortProperty={{controller.sortProperty}} @sortDescending={{controller.sortDescending}} @class="is-striped" as |t|> 524 <t.head> 525 <t.sort-by @prop="name">Name</t.sort-by> 526 <t.sort-by @prop="lang">Language</t.sort-by> 527 </t.head> 528 <t.body @key="model.name" as |row|> 529 <tr> 530 <td>{{row.model.name}}</td> 531 <td>{{row.model.lang}}</td> 532 </tr> 533 <tr> 534 <td colspan="2">{{row.model.desc}}</td> 535 </tr> 536 </t.body> 537 </ListTable> 538 <p class="annotation">The list-table component attempts to be as flexible as possible. For this reason, <code>t.body</code> does not provide the typical <code>tr</code> element. It's sometimes desired to have multiple elements per record.</p> 539 `, 540 context: { 541 injectRoutedController: injectRoutedController( 542 Controller.extend({ 543 queryParams: ['sortProperty', 'sortDescending'], 544 sortProperty: 'name', 545 sortDescending: false, 546 }) 547 ), 548 549 sortedShortList: computed( 550 'controller.{sortProperty,sortDescending}', 551 function () { 552 let sorted = productMetadata.sortBy( 553 this.get('controller.sortProperty') || 'name' 554 ); 555 return this.get('controller.sortDescending') 556 ? sorted.reverse() 557 : sorted; 558 } 559 ), 560 }, 561 }; 562 }; 563 564 export let Pagination = () => { 565 return { 566 template: hbs` 567 <h5 class="title is-5">Table pagination</h5> 568 <ListPagination @source={{longList}} @size={{5}} @page={{controller.currentPage}} as |p|> 569 <ListTable @source={{p.list}} @class="with-foot" as |t|> 570 <t.head> 571 <th class="is-1">Rank</th> 572 <th>City</th> 573 <th>State</th> 574 <th>Population</th> 575 <th>Growth</th> 576 </t.head> 577 <t.body @key="model.rank" as |row|> 578 <tr> 579 <td>{{row.model.rank}}</td> 580 <td>{{row.model.city}}</td> 581 <td>{{row.model.state}}</td> 582 <td>{{row.model.population}}</td> 583 <td>{{format-percentage row.model.growth total=1}}</td> 584 </tr> 585 </t.body> 586 </ListTable> 587 <div class="table-foot"> 588 <nav class="pagination"> 589 <span class="bumper-left">U.S. City population and growth from 2000-2013</span> 590 <div class="pagination-numbers"> 591 {{p.startsAt}}–{{p.endsAt}} of {{longList.length}} 592 </div> 593 <p.prev @class="pagination-previous"> < </p.prev> 594 <p.next @class="pagination-next"> > </p.next> 595 <ul class="pagination-list"></ul> 596 </nav> 597 </div> 598 </ListPagination> 599 <p class="annotation">Pagination works like sorting: using <code>link-to</code>s to set a query param.</p> 600 <p class="annotation">Pagination, like Table, is a minimal design. Only a next and previous button are available. The current place in the set of pages is tracked by showing which slice of items is currently shown.</p> 601 <p class="annotation">The pagination component exposes first and last components (for jumping to the beginning and end of a list) as well as pageLinks for generating links around the current page.</p> 602 `, 603 context: { 604 injectRoutedController: injectRoutedController( 605 Controller.extend({ 606 queryParams: ['currentPage'], 607 currentPage: 1, 608 }) 609 ), 610 longList, 611 }, 612 }; 613 }; 614 615 export let RowLinks = () => { 616 return { 617 template: hbs` 618 <h5 class="title is-5">Table row links</h5> 619 <ListTable @source={{shortList}} as |t|> 620 <t.head> 621 <th>Name</th> 622 <th>Language</th> 623 <th>Description</th> 624 </t.head> 625 <t.body @key="model.name" as |row|> 626 <tr class="is-interactive"> 627 <td><a href="javascript:;" class="is-primary">{{row.model.name}}</a></td> 628 <td>{{row.model.lang}}</td> 629 <td>{{row.model.desc}}</td> 630 </tr> 631 </t.body> 632 </ListTable> 633 <p class="annotation">It is common for tables to act as lists of links, (e.g., clients list all allocations, each row links to the allocation detail). The helper class <code>is-interactive</code> on the <code>tr</code> makes table rows have a pointer cursor. The helper class <code>is-primary</code> on the <code>a</code> element in a table row makes the link bold and black instead of blue. This makes the link stand out less, since the entire row is a link.</p> 634 <p class="annotation"> 635 A few rules for using table row links: 636 <ol> 637 <li>The <code>is-primary</code> cell should always be the first cell</li> 638 <li>The <code>is-primary</code> cell should always contain a link to the destination in the form of an <code>a</code> element. This is to support opening a link in a new tab.</li> 639 <li>The full row should transition to the destination on click. This is to improve the usability of a table by creating a larger click area.</li> 640 </ol> 641 </p> 642 `, 643 context: { 644 shortList: productMetadata, 645 }, 646 }; 647 }; 648 649 export let CellLinks = () => { 650 return { 651 template: hbs` 652 <h5 class="title is-5">Table cell links</h5> 653 <ListTable @source={{shortList}} as |t|> 654 <t.head> 655 <th>Name</th> 656 <th>Language</th> 657 <th>Description</th> 658 </t.head> 659 <t.body @key="model.name" as |row|> 660 <tr> 661 <td><a href={{row.model.link}} target="_parent">{{row.model.name}}</a></td> 662 <td>{{row.model.lang}}</td> 663 <td>{{row.model.desc}}</td> 664 </tr> 665 </t.body> 666 </ListTable> 667 <p class="annotation">Links in table cells are just links.</p> 668 `, 669 context: { 670 shortList: productMetadata, 671 }, 672 }; 673 }; 674 675 export let CellDecorations = () => { 676 return { 677 template: hbs` 678 <h5 class="title is-5">Table cell decorations</h5> 679 <ListTable @source={{shortList}} as |t|> 680 <t.head> 681 <th>Name</th> 682 <th>Language</th> 683 <th>Description</th> 684 </t.head> 685 <t.body @key="model.name" as |row|> 686 <tr> 687 <td><a href={{row.model.link}}>{{row.model.name}}</a></td> 688 <td class="nowrap"> 689 <span class="color-swatch 690 {{if (eq row.model.lang "ruby") "swatch-6"}} 691 {{if (eq row.model.lang "golang") "swatch-5"}}" /> 692 {{row.model.lang}} 693 </td> 694 <td>{{row.model.desc}}</td> 695 </tr> 696 </t.body> 697 </ListTable> 698 <p class="annotation">Small icons and accents of color make tables easier to scan.</p> 699 `, 700 context: { 701 shortList: productMetadata, 702 }, 703 }; 704 }; 705 706 export let CellIcons = () => { 707 return { 708 template: hbs` 709 <h5 class="title is-5">Table cell icons</h5> 710 <ListPagination @source={{longList}} @size={{5}} @page={{controller.currentPage}} as |p|> 711 <ListTable @source={{p.list}} @class="with-foot" as |t|> 712 <t.head> 713 <th class="is-narrow"></th> 714 <th class="is-1">Rank</th> 715 <th>City</th> 716 <th>State</th> 717 <th>Population</th> 718 <th>Growth</th> 719 </t.head> 720 <t.body @key="model.rank" as |row|> 721 <tr> 722 <td class="is-narrow"> 723 {{#if (lt row.model.growth 0)}} 724 {{x-icon "alert-triangle" class="is-warning"}} 725 {{/if}} 726 </td> 727 <td>{{row.model.rank}}</td> 728 <td>{{row.model.city}}</td> 729 <td>{{row.model.state}}</td> 730 <td>{{row.model.population}}</td> 731 <td>{{format-percentage row.model.growth total=1}}</td> 732 </tr> 733 </t.body> 734 </ListTable> 735 <div class="table-foot"> 736 <nav class="pagination"> 737 <span class="bumper-left">U.S. City population and growth from 2000-2013. Cities with negative growth denoted.</span> 738 <div class="pagination-numbers"> 739 {{p.startsAt}}–{{p.endsAt}} of {{longList.length}} 740 </div> 741 <p.prev @class="pagination-previous"> < </p.prev> 742 <p.next @class="pagination-next"> > </p.next> 743 <ul class="pagination-list"></ul> 744 </nav> 745 </div> 746 </ListPagination> 747 `, 748 context: { 749 injectRoutedController: injectRoutedController( 750 Controller.extend({ 751 queryParams: ['currentPage'], 752 currentPage: 1, 753 }) 754 ), 755 longList, 756 }, 757 }; 758 };