{
  "id": "FI04",
  "title": "Tiles as filters",
  "topic": "filter",
  "use": "The tiles above the list are the filter. Each one carries a number and a status; a click narrows the list below to it, and several tiles combine. Use them when one dimension structures the work queue and its numbers are needed anyway. Everything else stays in the drawer.",
  "decisions": [
    "The status belongs to the tiles and is not offered in the drawer as well: a dimension in two places lets the same value be filtered twice, and the two displays contradict each other. The drawer holds the other dimensions.",
    "Several tiles combine as OR — Open and Error pressed shows both, not none: within one dimension values add up, across dimensions they narrow. That is why the tiles are selectable and not a row in which one replaces the other.",
    "A selectable card is a checkbox, which is what multiple selection says; an interactive card would be a stateless button — right for a tile that opens another page, wrong for a switch that stays pressed.",
    "The pressed tile has its badge under the toolbar, because the badge row is the one place where everything set stands side by side, the drawer's filters included; removing the badge releases the tile. The tile shows its switch, the badge the state of the list.",
    "No All tile: all is the state with no tile pressed, and a tile that selects nothing would be a switch for switching off.",
    "The number on a tile is the stock of its status, independent of search and drawer filters: a number that moves with every restriction would be a second hit count, which is the toolbar count's job.",
    "Sorting happens in the column header, not in a dropdown beside the table: the grid brings hover, keyboard and the count."
  ],
  "try": [
    {
      "do": "Press Error",
      "then": "the list shows four documents, the tile stays pressed, and the badge “Status: Error” stands under the toolbar"
    },
    {
      "do": "Press Open as well",
      "then": "35 documents — the values of one dimension add up, they do not narrow each other"
    },
    {
      "do": "Remove the Status badge",
      "then": "the tiles are released with it, not only the list"
    },
    {
      "do": "Open the filter",
      "then": "the drawer holds document type, cost center and processed by — not the status, which belongs to the tiles"
    },
    {
      "do": "Press Open and choose Cash receipt in the drawer",
      "then": "now it narrows: the statuses combine with or, the document type on top"
    },
    {
      "do": "Error and Cash receipt together",
      "then": "no match, and the empty state names both restrictions and the reset that lifts them"
    },
    {
      "do": "Click a column header",
      "then": "the list sorts there, not in a dropdown above the table"
    }
  ],
  "revision": 1,
  "changes": [],
  "reviewed": true,
  "source": "<!-- Demo only, not part of the pattern. The table takes the height the screen leaves it; here it is set to fit the frame. -->\n\n<div class=\"ed-stack ed-gap-5\">\n  <!-- Demo only, not part of the pattern. The screen's title, and whose data it shows, come from the screen around the pattern; SC01 places them. -->\n\n  <div id=\"fi04-tiles\" class=\"ed-grid-fill ed-gap-4\" role=\"group\" aria-label=\"Documents by status\" style=\"--ed-grid-min-column-size: 11rem\">\n    <ed-card selectable data-status=\"Open\" aria-labelledby=\"fi04-open fi04-open-count\">\n      <ed-icon slot=\"header-start\" name=\"clock\"></ed-icon>\n      <span slot=\"header\" id=\"fi04-open\">Open</span>\n      <div class=\"ed-kpi\" id=\"fi04-open-count\">\n        <span class=\"ed-kpi-value\"></span>\n        <span>documents</span>\n      </div>\n    </ed-card>\n    <ed-card selectable data-status=\"Reviewed\" aria-labelledby=\"fi04-reviewed fi04-reviewed-count\">\n      <ed-icon slot=\"header-start\" name=\"check_circle\"></ed-icon>\n      <span slot=\"header\" id=\"fi04-reviewed\">Reviewed</span>\n      <div class=\"ed-kpi\" id=\"fi04-reviewed-count\">\n        <span class=\"ed-kpi-value\"></span>\n        <span>documents</span>\n      </div>\n    </ed-card>\n    <ed-card selectable data-status=\"Finalized\" aria-labelledby=\"fi04-finalized fi04-finalized-count\">\n      <ed-icon slot=\"header-start\" name=\"lock\"></ed-icon>\n      <span slot=\"header\" id=\"fi04-finalized\">Finalized</span>\n      <div class=\"ed-kpi\" id=\"fi04-finalized-count\">\n        <span class=\"ed-kpi-value\"></span>\n        <span>documents</span>\n      </div>\n    </ed-card>\n    <ed-card selectable data-status=\"Error\" aria-labelledby=\"fi04-error fi04-error-count\">\n      <ed-icon slot=\"header-start\" name=\"warning\"></ed-icon>\n      <span slot=\"header\" id=\"fi04-error\">Error</span>\n      <div class=\"ed-kpi\" id=\"fi04-error-count\">\n        <span class=\"ed-kpi-value\"></span>\n        <span>documents</span>\n      </div>\n    </ed-card>\n  </div>\n\n  <ed-data-grid id=\"fi04-documents\" label=\"Documents\" with-search data-ed-preload=\"ed-format-date\">\n    <ed-button slot=\"toolbar-actions\">\n      <ed-icon slot=\"start\" name=\"plus\"></ed-icon>\n      Create document\n    </ed-button>\n    <ed-button id=\"fi04-filter\" slot=\"toolbar-view\" appearance=\"plain\">\n      <ed-icon slot=\"start\" name=\"filter\"></ed-icon>\n      <span id=\"fi04-filter-label\">Filter</span>\n    </ed-button>\n    <ed-empty-state slot=\"no-results\" icon=\"search\" size=\"small\">\n      <span slot=\"label\">No document in this selection</span>\n      <span id=\"fi04-reason\"></span>\n    </ed-empty-state>\n  </ed-data-grid>\n\n  <!-- The status belongs to the tiles and is not offered here as well. -->\n  <ed-drawer id=\"fi04-drawer\" label=\"Filter\" placement=\"end\" light-dismiss with-footer>\n    <div class=\"ed-stack\">\n      <ed-select data-column=\"type\" label=\"Document type\" placeholder=\"all\" multiple with-clear>\n        <ed-option value=\"Bank voucher\">Bank voucher</ed-option>\n        <ed-option value=\"Cash receipt\">Cash receipt</ed-option>\n        <ed-option value=\"Incoming invoice\">Incoming invoice</ed-option>\n        <ed-option value=\"Journal entry\">Journal entry</ed-option>\n        <ed-option value=\"Outgoing invoice\">Outgoing invoice</ed-option>\n      </ed-select>\n      <ed-select data-column=\"costCenter\" label=\"Cost center\" placeholder=\"all\" multiple with-clear>\n        <ed-option value=\"4700\">4700</ed-option>\n        <ed-option value=\"4711\">4711</ed-option>\n        <ed-option value=\"4712\">4712</ed-option>\n        <ed-option value=\"4713\">4713</ed-option>\n        <ed-option value=\"4714\">4714</ed-option>\n        <ed-option value=\"4715\">4715</ed-option>\n        <ed-option value=\"4716\">4716</ed-option>\n      </ed-select>\n      <ed-select data-column=\"clerk\" label=\"Processed by\" placeholder=\"all\" multiple with-clear>\n        <ed-option value=\"Andrea Jansen\">Andrea Jansen</ed-option>\n        <ed-option value=\"Marie-Luise Hoffmann-Grote\">Marie-Luise Hoffmann-Grote</ed-option>\n        <ed-option value=\"Sophie Brandt\">Sophie Brandt</ed-option>\n        <ed-option value=\"Thorsten Reinke\">Thorsten Reinke</ed-option>\n      </ed-select>\n    </div>\n    <ed-button slot=\"footer\" data-drawer=\"close\">Done</ed-button>\n    <ed-button id=\"fi04-drawer-reset\" slot=\"footer\" appearance=\"plain\">Reset all filters</ed-button>\n  </ed-drawer>\n</div>\n\n<!-- Demo only, not part of the pattern. The product supplies documents: the documents of the list, as { id, date, text, type, costCenter, amount, status, clerk }, with date as YYYY-MM-DD. -->\n\n<script type=\"module\">\n  await customElements.whenDefined('ed-data-grid');\n\n  const grid = document.getElementById('fi04-documents');\n  const drawer = document.getElementById('fi04-drawer');\n  const tiles = [...document.querySelectorAll('#fi04-tiles ed-card')];\n  const fields = [...drawer.querySelectorAll('ed-select')];\n  const filterLabel = document.getElementById('fi04-filter-label');\n  const reason = document.getElementById('fi04-reason');\n\n  const euro = new Intl.NumberFormat('en', { style: 'currency', currency: 'EUR' });\n  const date = value => Object.assign(document.createElement('ed-format-date'), { date: value, month: 'short', day: 'numeric', year: 'numeric' });\n  const statusVariant = { Open: 'neutral', Reviewed: 'info', Finalized: 'success', Error: 'danger' };\n  const labels = { status: 'Status', type: 'Document type', costCenter: 'Cost center', clerk: 'Processed by' };\n\n  const set = { filterable: true, filterType: 'set' };\n  grid.columns = [\n    { field: 'id', label: 'Document', width: 160, rowheader: true },\n    { field: 'date', label: 'Date', width: 112, formatter: date },\n    { field: 'text', label: 'Posting text', minWidth: 192 },\n    { field: 'type', label: labels.type, width: 176, ...set },\n    { field: 'costCenter', label: labels.costCenter, width: 128, ...set },\n    { field: 'amount', label: 'Amount', width: 128, align: 'end', formatter: value => euro.format(value) },\n    {\n      field: 'status',\n      label: labels.status,\n      width: 160,\n      ...set,\n      formatter: value => {\n        const badge = document.createElement('ed-badge');\n        badge.setAttribute('variant', statusVariant[value]);\n        badge.textContent = value;\n        return badge;\n      }\n    },\n    // The drawer filters by who processed a document, a column the list does not show.\n    { field: 'clerk', label: labels.clerk, hidden: true, hideable: false, ...set }\n  ];\n  grid.data = documents;\n\n  // A tile counts the stock of its status, not the rows that search and drawer leave.\n  for (const tile of tiles) {\n    tile.querySelector('.ed-kpi-value').textContent = documents.filter(row => row.status === tile.dataset.status).length;\n  }\n\n  const valuesOf = id => grid.filters.find(filter => filter.id === id)?.value ?? [];\n\n  // The button counts what is set in the drawer; the empty state names every value set and the\n  // search, so it is not read as \"there is no data\". The grid shows its reset only beside filter\n  // badges, so a search alone gets no pointer to it.\n  function describe() {\n    const inDrawer = fields.reduce((sum, field) => sum + valuesOf(field.dataset.column).length, 0);\n    filterLabel.textContent = inDrawer ? `Filter · ${inDrawer}` : 'Filter';\n    const active = Object.keys(labels).flatMap(id => valuesOf(id).map(value => `${labels[id]}: ${value}`));\n    if (grid.searchTerm.trim()) active.push(`Search: ${grid.searchTerm.trim()}`);\n    const reset = grid.filters.length ? ' The reset in the row above clears everything.' : '';\n    reason.textContent = `Set: ${active.join(' · ')}.${reset}`;\n  }\n\n  function sync() {\n    for (const tile of tiles) tile.checked = valuesOf('status').includes(tile.dataset.status);\n    for (const field of fields) field.value = valuesOf(field.dataset.column);\n    describe();\n  }\n\n  function setColumn(id, values) {\n    const others = grid.filters.filter(filter => filter.id !== id);\n    grid.filters = values.length ? [...others, { id, value: values }] : others;\n    describe();\n  }\n\n  for (const tile of tiles) {\n    tile.addEventListener('change', () =>\n      setColumn('status', tiles.filter(candidate => candidate.checked).map(candidate => candidate.dataset.status))\n    );\n  }\n\n  for (const field of fields) {\n    field.addEventListener('change', () => setColumn(field.dataset.column, field.value ?? []));\n  }\n\n  grid.addEventListener('ed-data-grid-filter-change', sync);\n\n  // The drawer is modal, so the button behind it cannot be pressed again while it is open: it only\n  // opens, and Done, the backdrop and Escape close.\n  document.getElementById('fi04-filter').addEventListener('click', () => {\n    drawer.open = true;\n  });\n\n  // Reset all filters clears everything set, the tiles included, as the reset beside the badges does.\n  document.getElementById('fi04-drawer-reset').addEventListener('click', () => {\n    grid.filters = [];\n    sync();\n  });\n\n  describe();\n</script>",
  "page": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/filter/fi04-tiles-as-filters/",
  "topicPage": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/filter/"
}
