github.com/thomasobenaus/nomad@v0.11.1/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      let router = container.lookup('router:main');
    33      router.initialURL = 'storybook';
    34      router.startRouting(true);
    35  
    36      this.set('controller', container.lookup('controller:storybook'));
    37    });
    38  }
    39  
    40  let longList = [
    41    { city: 'New York', growth: 0.048, population: '8405837', rank: '1', state: 'New York' },
    42    { city: 'Los Angeles', growth: 0.048, population: '3884307', rank: '2', state: 'California' },
    43    { city: 'Chicago', growth: -0.061, population: '2718782', rank: '3', state: 'Illinois' },
    44    { city: 'Houston', growth: 0.11, population: '2195914', rank: '4', state: 'Texas' },
    45    {
    46      city: 'Philadelphia',
    47      growth: 0.026,
    48      population: '1553165',
    49      rank: '5',
    50      state: 'Pennsylvania',
    51    },
    52    { city: 'Phoenix', growth: 0.14, population: '1513367', rank: '6', state: 'Arizona' },
    53    { city: 'San Antonio', growth: 0.21, population: '1409019', rank: '7', state: 'Texas' },
    54    { city: 'San Diego', growth: 0.105, population: '1355896', rank: '8', state: 'California' },
    55    { city: 'Dallas', growth: 0.056, population: '1257676', rank: '9', state: 'Texas' },
    56    { city: 'San Jose', growth: 0.105, population: '998537', rank: '10', state: 'California' },
    57    { city: 'Austin', growth: 0.317, population: '885400', rank: '11', state: 'Texas' },
    58    { city: 'Indianapolis', growth: 0.078, population: '843393', rank: '12', state: 'Indiana' },
    59    { city: 'Jacksonville', growth: 0.143, population: '842583', rank: '13', state: 'Florida' },
    60    {
    61      city: 'San Francisco',
    62      growth: 0.077,
    63      population: '837442',
    64      rank: '14',
    65      state: 'California',
    66    },
    67    { city: 'Columbus', growth: 0.148, population: '822553', rank: '15', state: 'Ohio' },
    68    {
    69      city: 'Charlotte',
    70      growth: 0.391,
    71      population: '792862',
    72      rank: '16',
    73      state: 'North Carolina',
    74    },
    75    { city: 'Fort Worth', growth: 0.451, population: '792727', rank: '17', state: 'Texas' },
    76    { city: 'Detroit', growth: -0.271, population: '688701', rank: '18', state: 'Michigan' },
    77    { city: 'El Paso', growth: 0.194, population: '674433', rank: '19', state: 'Texas' },
    78    { city: 'Memphis', growth: -0.053, population: '653450', rank: '20', state: 'Tennessee' },
    79    { city: 'Seattle', growth: 0.156, population: '652405', rank: '21', state: 'Washington' },
    80    { city: 'Denver', growth: 0.167, population: '649495', rank: '22', state: 'Colorado' },
    81    {
    82      city: 'Washington',
    83      growth: 0.13,
    84      population: '646449',
    85      rank: '23',
    86      state: 'District of Columbia',
    87    },
    88    { city: 'Boston', growth: 0.094, population: '645966', rank: '24', state: 'Massachusetts' },
    89    {
    90      city: 'Nashville-Davidson',
    91      growth: 0.162,
    92      population: '634464',
    93      rank: '25',
    94      state: 'Tennessee',
    95    },
    96    { city: 'Baltimore', growth: -0.04, population: '622104', rank: '26', state: 'Maryland' },
    97    { city: 'Oklahoma City', growth: 0.202, population: '610613', rank: '27', state: 'Oklahoma' },
    98    {
    99      city: 'Louisville/Jefferson County',
   100      growth: 0.1,
   101      population: '609893',
   102      rank: '28',
   103      state: 'Kentucky',
   104    },
   105    { city: 'Portland', growth: 0.15, population: '609456', rank: '29', state: 'Oregon' },
   106    { city: 'Las Vegas', growth: 0.245, population: '603488', rank: '30', state: 'Nevada' },
   107    { city: 'Milwaukee', growth: 0.003, population: '599164', rank: '31', state: 'Wisconsin' },
   108    { city: 'Albuquerque', growth: 0.235, population: '556495', rank: '32', state: 'New Mexico' },
   109    { city: 'Tucson', growth: 0.075, population: '526116', rank: '33', state: 'Arizona' },
   110    { city: 'Fresno', growth: 0.183, population: '509924', rank: '34', state: 'California' },
   111    { city: 'Sacramento', growth: 0.172, population: '479686', rank: '35', state: 'California' },
   112    { city: 'Long Beach', growth: 0.015, population: '469428', rank: '36', state: 'California' },
   113    { city: 'Kansas City', growth: 0.055, population: '467007', rank: '37', state: 'Missouri' },
   114    { city: 'Mesa', growth: 0.135, population: '457587', rank: '38', state: 'Arizona' },
   115    { city: 'Virginia Beach', growth: 0.051, population: '448479', rank: '39', state: 'Virginia' },
   116    { city: 'Atlanta', growth: 0.062, population: '447841', rank: '40', state: 'Georgia' },
   117    {
   118      city: 'Colorado Springs',
   119      growth: 0.214,
   120      population: '439886',
   121      rank: '41',
   122      state: 'Colorado',
   123    },
   124    { city: 'Omaha', growth: 0.059, population: '434353', rank: '42', state: 'Nebraska' },
   125    { city: 'Raleigh', growth: 0.487, population: '431746', rank: '43', state: 'North Carolina' },
   126    { city: 'Miami', growth: 0.149, population: '417650', rank: '44', state: 'Florida' },
   127    { city: 'Oakland', growth: 0.013, population: '406253', rank: '45', state: 'California' },
   128    { city: 'Minneapolis', growth: 0.045, population: '400070', rank: '46', state: 'Minnesota' },
   129    { city: 'Tulsa', growth: 0.013, population: '398121', rank: '47', state: 'Oklahoma' },
   130    { city: 'Cleveland', growth: -0.181, population: '390113', rank: '48', state: 'Ohio' },
   131    { city: 'Wichita', growth: 0.097, population: '386552', rank: '49', state: 'Kansas' },
   132    { city: 'Arlington', growth: 0.133, population: '379577', rank: '50', state: 'Texas' },
   133  ];
   134  
   135  export let Standard = () => {
   136    return {
   137      template: hbs`
   138        <h5 class="title is-5">Table</h5>
   139        <ListTable @source={{shortList}} as |t|>
   140          <t.head>
   141            <th>Name</th>
   142            <th>Language</th>
   143            <th>Description</th>
   144          </t.head>
   145          <t.body @key="model.name" as |row|>
   146            <tr>
   147              <td>{{row.model.name}}</td>
   148              <td>{{row.model.lang}}</td>
   149              <td>{{row.model.desc}}</td>
   150            </tr>
   151          </t.body>
   152        </ListTable>
   153        <p class="annotation">Tables have airy designs with a minimal amount of borders. This maximizes their utility.</p>
   154        `,
   155      context: {
   156        shortList: productMetadata,
   157      },
   158    };
   159  };
   160  
   161  export let Search = () => {
   162    return {
   163      template: hbs`
   164        <h5 class="title is-5">Table search</h5>
   165        <div class="boxed-section">
   166          <div class="boxed-section-head">
   167            Table Name
   168            <SearchBox
   169              @searchTerm={{mut controller.searchTerm}}
   170              @placeholder="Search..."
   171              @class="is-inline pull-right"
   172              @inputClass="is-compact" />
   173          </div>
   174          <div class="boxed-section-body {{if controller.filteredShortList.length "is-full-bleed"}}">
   175            {{#if controller.filteredShortList.length}}
   176              <ListTable @source={{controller.filteredShortList}} as |t|>
   177                <t.head>
   178                  <th>Name</th>
   179                  <th>Language</th>
   180                  <th>Description</th>
   181                </t.head>
   182                <t.body @key="model.name" as |row|>
   183                  <tr>
   184                    <td>{{row.model.name}}</td>
   185                    <td>{{row.model.lang}}</td>
   186                    <td>{{row.model.desc}}</td>
   187                  </tr>
   188                </t.body>
   189              </ListTable>
   190            {{else}}
   191              <div class="empty-message">
   192                <h3 class="empty-message-headline">No Matches</h3>
   193                <p class="empty-message-body">No products match your query.</p>
   194              </div>
   195            {{/if}}
   196          </div>
   197        </div>
   198        <p class="annotation">Tables compose with boxed-section and boxed-section composes with search box.</p>
   199        `,
   200      context: {
   201        controller: EmberObject.extend({
   202          searchTerm: '',
   203  
   204          filteredShortList: computed('searchTerm', function() {
   205            let term = this.searchTerm.toLowerCase();
   206            return productMetadata.filter(product => product.name.toLowerCase().includes(term));
   207          }),
   208        }).create(),
   209      },
   210    };
   211  };
   212  
   213  export let SortableColumns = () => {
   214    return {
   215      template: hbs`
   216        <h5 class="title is-5">Table with sortable columns</h5>
   217        <ListTable @source={{sortedShortList}} @sortProperty={{controller.sortProperty}} @sortDescending={{controller.sortDescending}} as |t|>
   218          <t.head>
   219            <t.sort-by @prop="name">Name</t.sort-by>
   220            <t.sort-by @prop="lang" @class="is-2">Language</t.sort-by>
   221            <th>Description</th>
   222          </t.head>
   223          <t.body @key="model.name" as |row|>
   224            <tr>
   225              <td>{{row.model.name}}</td>
   226              <td>{{row.model.lang}}</td>
   227              <td>{{row.model.desc}}</td>
   228            </tr>
   229          </t.body>
   230        </ListTable>
   231        <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>
   232        <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>
   233        `,
   234      context: {
   235        injectRoutedController: injectRoutedController(
   236          Controller.extend({
   237            queryParams: ['sortProperty', 'sortDescending'],
   238            sortProperty: 'name',
   239            sortDescending: false,
   240          })
   241        ),
   242  
   243        sortedShortList: computed('controller.sortProperty', 'controller.sortDescending', function() {
   244          let sorted = productMetadata.sortBy(this.get('controller.sortProperty') || 'name');
   245          return this.get('controller.sortDescending') ? sorted.reverse() : sorted;
   246        }),
   247      },
   248    };
   249  };
   250  
   251  export let MultiRow = () => {
   252    return {
   253      template: hbs`
   254        <h5 class="title is-5">Multi-row Table</h5>
   255        <ListTable @source={{sortedShortList}} @sortProperty={{controller.sortProperty}} @sortDescending={{controller.sortDescending}} @class="is-striped" as |t|>
   256          <t.head>
   257            <t.sort-by @prop="name">Name</t.sort-by>
   258            <t.sort-by @prop="lang">Language</t.sort-by>
   259          </t.head>
   260          <t.body @key="model.name" as |row|>
   261            <tr>
   262              <td>{{row.model.name}}</td>
   263              <td>{{row.model.lang}}</td>
   264            </tr>
   265            <tr>
   266              <td colspan="2">{{row.model.desc}}</td>
   267            </tr>
   268          </t.body>
   269        </ListTable>
   270        <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>
   271        `,
   272      context: {
   273        injectRoutedController: injectRoutedController(
   274          Controller.extend({
   275            queryParams: ['sortProperty', 'sortDescending'],
   276            sortProperty: 'name',
   277            sortDescending: false,
   278          })
   279        ),
   280  
   281        sortedShortList: computed('controller.sortProperty', 'controller.sortDescending', function() {
   282          let sorted = productMetadata.sortBy(this.get('controller.sortProperty') || 'name');
   283          return this.get('controller.sortDescending') ? sorted.reverse() : sorted;
   284        }),
   285      },
   286    };
   287  };
   288  
   289  export let Pagination = () => {
   290    return {
   291      template: hbs`
   292        <h5 class="title is-5">Table pagination</h5>
   293        <ListPagination @source={{longList}} @size={{5}} @page={{controller.currentPage}} as |p|>
   294          <ListTable @source={{p.list}} @class="with-foot" as |t|>
   295            <t.head>
   296              <th class="is-1">Rank</th>
   297              <th>City</th>
   298              <th>State</th>
   299              <th>Population</th>
   300              <th>Growth</th>
   301            </t.head>
   302            <t.body @key="model.rank" as |row|>
   303              <tr>
   304                <td>{{row.model.rank}}</td>
   305                <td>{{row.model.city}}</td>
   306                <td>{{row.model.state}}</td>
   307                <td>{{row.model.population}}</td>
   308                <td>{{format-percentage row.model.growth total=1}}</td>
   309              </tr>
   310            </t.body>
   311          </ListTable>
   312          <div class="table-foot">
   313            <nav class="pagination">
   314              <span class="bumper-left">U.S. City population and growth from 2000-2013</span>
   315              <div class="pagination-numbers">
   316                {{p.startsAt}}&ndash;{{p.endsAt}} of {{longList.length}}
   317              </div>
   318                <p.prev @class="pagination-previous"> &lt; </p.prev>
   319                <p.next @class="pagination-next"> &gt; </p.next>
   320                <ul class="pagination-list"></ul>
   321            </nav>
   322          </div>
   323        </ListPagination>
   324        <p class="annotation">Pagination works like sorting: using <code>link-to</code>s to set a query param.</p>
   325        <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>
   326        <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>
   327        `,
   328      context: {
   329        injectRoutedController: injectRoutedController(
   330          Controller.extend({
   331            queryParams: ['currentPage'],
   332            currentPage: 1,
   333          })
   334        ),
   335        longList,
   336      },
   337    };
   338  };
   339  
   340  export let RowLinks = () => {
   341    return {
   342      template: hbs`
   343        <h5 class="title is-5">Table row links</h5>
   344        <ListTable @source={{shortList}} as |t|>
   345          <t.head>
   346            <th>Name</th>
   347            <th>Language</th>
   348            <th>Description</th>
   349          </t.head>
   350          <t.body @key="model.name" as |row|>
   351            <tr class="is-interactive">
   352              <td><a href="javascript:;" class="is-primary">{{row.model.name}}</a></td>
   353              <td>{{row.model.lang}}</td>
   354              <td>{{row.model.desc}}</td>
   355            </tr>
   356          </t.body>
   357        </ListTable>
   358        <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>
   359        <p class="annotation">
   360          A few rules for using table row links:
   361          <ol>
   362            <li>The <code>is-primary</code> cell should always be the first cell</li>
   363            <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>
   364            <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>
   365          </ol>
   366        </p>
   367        `,
   368      context: {
   369        shortList: productMetadata,
   370      },
   371    };
   372  };
   373  
   374  export let CellLinks = () => {
   375    return {
   376      template: hbs`
   377        <h5 class="title is-5">Table cell links</h5>
   378        <ListTable @source={{shortList}} as |t|>
   379          <t.head>
   380            <th>Name</th>
   381            <th>Language</th>
   382            <th>Description</th>
   383          </t.head>
   384          <t.body @key="model.name" as |row|>
   385            <tr>
   386              <td><a href={{row.model.link}} target="_parent">{{row.model.name}}</a></td>
   387              <td>{{row.model.lang}}</td>
   388              <td>{{row.model.desc}}</td>
   389            </tr>
   390          </t.body>
   391        </ListTable>
   392        <p class="annotation">Links in table cells are just links.</p>
   393        `,
   394      context: {
   395        shortList: productMetadata,
   396      },
   397    };
   398  };
   399  
   400  export let CellDecorations = () => {
   401    return {
   402      template: hbs`
   403        <h5 class="title is-5">Table cell decorations</h5>
   404        <ListTable @source={{shortList}} as |t|>
   405          <t.head>
   406            <th>Name</th>
   407            <th>Language</th>
   408            <th>Description</th>
   409          </t.head>
   410          <t.body @key="model.name" as |row|>
   411            <tr>
   412              <td><a href={{row.model.link}}>{{row.model.name}}</a></td>
   413              <td class="nowrap">
   414                <span class="color-swatch
   415                  {{if (eq row.model.lang "ruby") "swatch-6"}}
   416                  {{if (eq row.model.lang "golang") "swatch-5"}}" />
   417                {{row.model.lang}}
   418              </td>
   419              <td>{{row.model.desc}}</td>
   420            </tr>
   421          </t.body>
   422        </ListTable>
   423        <p class="annotation">Small icons and accents of color make tables easier to scan.</p>
   424        `,
   425      context: {
   426        shortList: productMetadata,
   427      },
   428    };
   429  };
   430  
   431  export let CellIcons = () => {
   432    return {
   433      template: hbs`
   434        <h5 class="title is-5">Table cell icons</h5>
   435        <ListPagination @source={{longList}} @size={{5}} @page={{controller.currentPage}} as |p|>
   436          <ListTable @source={{p.list}} @class="with-foot" as |t|>
   437            <t.head>
   438              <th class="is-narrow"></th>
   439              <th class="is-1">Rank</th>
   440              <th>City</th>
   441              <th>State</th>
   442              <th>Population</th>
   443              <th>Growth</th>
   444            </t.head>
   445            <t.body @key="model.rank" as |row|>
   446              <tr>
   447                <td class="is-narrow">
   448                  {{#if (lt row.model.growth 0)}}
   449                    {{x-icon "warning" class="is-warning"}}
   450                  {{/if}}
   451                </td>
   452                <td>{{row.model.rank}}</td>
   453                <td>{{row.model.city}}</td>
   454                <td>{{row.model.state}}</td>
   455                <td>{{row.model.population}}</td>
   456                <td>{{format-percentage row.model.growth total=1}}</td>
   457              </tr>
   458            </t.body>
   459          </ListTable>
   460          <div class="table-foot">
   461            <nav class="pagination">
   462              <span class="bumper-left">U.S. City population and growth from 2000-2013. Cities with negative growth denoted.</span>
   463              <div class="pagination-numbers">
   464                {{p.startsAt}}&ndash;{{p.endsAt}} of {{longList.length}}
   465              </div>
   466                <p.prev @class="pagination-previous"> &lt; </p.prev>
   467                <p.next @class="pagination-next"> &gt; </p.next>
   468                <ul class="pagination-list"></ul>
   469            </nav>
   470          </div>
   471        </ListPagination>
   472        `,
   473      context: {
   474        injectRoutedController: injectRoutedController(
   475          Controller.extend({
   476            queryParams: ['currentPage'],
   477            currentPage: 1,
   478          })
   479        ),
   480        longList,
   481      },
   482    };
   483  };