{
  "id": "FI02",
  "title": "Inline filter panel",
  "topic": "filter",
  "use": "The panel is the exception to the drawer. The same filter button opens it across the full width, directly under the toolbar. The content moves down, nothing is covered. It is allowed for at most four filters without groups. A second reason is a very wide table that a drawer would squeeze too much.",
  "decisions": [
    "The panel is the exception to the drawer, with three conditions: at most four filters of equal rank, no need for groups, and a user who tries values one after another — or a table so wide that a drawer would squeeze it. Anything more goes in the drawer.",
    "It is a band, not a menu: full width, in the flow, hanging on the toolbar with no gap, pushing the content down — never aligned to the button, floating, or over the table.",
    "The filter button says what it does — 'Filter' when closed, 'Close filter' when open — and a second click closes the panel.",
    "There is no Apply: every change takes effect at once, so the user sees the count change while trying values, which is what this surface is for.",
    "The count stays in the toolbar's state row even with no filter. Each filtered column adds a badge with dimension and value, followed by the reset, which goes away with the last badge: at the edge it would read as an action unrelated to the badges, and in the panel it would be out of reach once the panel is closed.",
    "The empty state names the filter as the cause and offers the reset in the same place."
  ],
  "try": [
    {
      "do": "Press the filter button",
      "then": "the panel opens across the full width and pushes the content down"
    },
    {
      "do": "Look at its edges",
      "then": "the panel does not float and lies over nothing"
    },
    {
      "do": "Set a filter",
      "then": "the count above it changes at once"
    },
    {
      "do": "Count the filters",
      "then": "more than four of equal rank belong in the drawer, not here"
    }
  ],
  "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-4\">\n  <!-- The panel hangs on the toolbar it belongs to: no gap between the two, the usual one before the table. -->\n  <div class=\"ed-stack ed-gap-0\">\n    <ed-toolbar id=\"fi02-toolbar\" label=\"Documents\">\n      <ed-input id=\"fi02-search\" slot=\"search\" type=\"search\" placeholder=\"Document number, posting text\" aria-label=\"Search documents\" with-clear></ed-input>\n      <ed-button>\n        <ed-icon slot=\"start\" name=\"plus\"></ed-icon>\n        Create document\n      </ed-button>\n      <ed-button id=\"fi02-filter\" slot=\"view\" appearance=\"plain\">\n        <ed-icon slot=\"start\" name=\"filter\"></ed-icon>\n        <span id=\"fi02-filter-label\">Filter</span>\n      </ed-button>\n      <span id=\"fi02-count\" slot=\"meta\"></span>\n    </ed-toolbar>\n\n    <section id=\"fi02-panel\" class=\"fi02-panel\" aria-labelledby=\"fi02-panel-title\" hidden>\n      <div class=\"ed-stack ed-gap-4\">\n        <h2 id=\"fi02-panel-title\" class=\"ed-heading-l\">Filter</h2>\n        <div class=\"ed-grid\" style=\"--ed-grid-min-column-size: 12rem\">\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=\"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          <ed-select data-column=\"status\" label=\"Status\" placeholder=\"All\" multiple with-clear>\n            <ed-option value=\"Error\">Error</ed-option>\n            <ed-option value=\"Finalized\">Finalized</ed-option>\n            <ed-option value=\"Open\">Open</ed-option>\n            <ed-option value=\"Reviewed\">Reviewed</ed-option>\n          </ed-select>\n        </div>\n      </div>\n    </section>\n  </div>\n\n  <ed-data-grid id=\"fi02-documents\" label=\"Documents\" without-toolbar>\n    <ed-empty-state slot=\"no-results\" icon=\"search\" size=\"medium\">\n      <span slot=\"label\">No document matches the active filters</span>\n      <span id=\"fi02-reason\"></span>\n      <ed-button id=\"fi02-empty-reset\" slot=\"button\" appearance=\"outlined\">\n        <ed-icon slot=\"start\" name=\"close\"></ed-icon>\n        Reset all filters\n      </ed-button>\n    </ed-empty-state>\n  </ed-data-grid>\n</div>\n\n<style>\n  .fi02-panel {\n    border: var(--ed-panel-border-width) var(--ed-panel-border-style) var(--ed-color-border);\n    border-radius: var(--ed-panel-border-radius);\n    padding: var(--ed-panel-spacing);\n  }\n</style>\n\n<!-- Demo only, not part of the pattern. The product supplies documents: the documents of the list, as { id, text, type, clerk, status }. -->\n\n<script type=\"module\">\n  await customElements.whenDefined('ed-data-grid');\n\n  const toolbar = document.getElementById('fi02-toolbar');\n  const search = document.getElementById('fi02-search');\n  const count = document.getElementById('fi02-count');\n  const panel = document.getElementById('fi02-panel');\n  const grid = document.getElementById('fi02-documents');\n  const reason = document.getElementById('fi02-reason');\n  const fields = [...panel.querySelectorAll('ed-select')];\n\n  const statusVariant = { Open: 'neutral', Reviewed: 'info', Finalized: 'success', Error: 'danger' };\n  const labels = { type: 'Document type', clerk: 'Processed by', status: 'Status' };\n  const entries = number => `${number} ${number === 1 ? 'entry' : 'entries'}`;\n\n  grid.columns = [\n    { field: 'id', label: 'Document', width: 152, rowheader: true, pinned: 'left' },\n    { field: 'text', label: 'Posting text', minWidth: 224 },\n    { field: 'type', label: labels.type, width: 176, filterable: true, filterType: 'set' },\n    { field: 'clerk', label: labels.clerk, width: 208, filterable: true, filterType: 'set' },\n    {\n      field: 'status',\n      label: labels.status,\n      width: 144,\n      pinned: 'right',\n      filterable: true,\n      filterType: '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  ];\n  grid.data = documents;\n\n  async function showState() {\n    await grid.updateComplete;\n    const total = grid.data.length;\n    const term = grid.searchTerm.trim();\n    const narrowed = grid.filteredCount !== total || term !== '';\n    count.textContent = narrowed ? `${grid.filteredCount} of ${entries(total)}` : entries(total);\n\n    // The empty state names what narrows the list, so it is not read as \"there is no data\".\n    const active = grid.filters.map(filter => `${labels[filter.id]} — ${filter.value.join(', ')}`);\n    if (term) active.push(`Search — ${term}`);\n    reason.textContent = `Active: ${active.join(' · ')}`;\n\n    for (const stale of toolbar.querySelectorAll('[slot=\"filters\"]')) stale.remove();\n    for (const filter of grid.filters) {\n      const badge = document.createElement('ed-badge');\n      badge.slot = 'filters';\n      badge.setAttribute('variant', 'neutral');\n      badge.setAttribute('pill', '');\n      badge.setAttribute('with-remove', '');\n      badge.textContent = `${labels[filter.id]}: ${filter.value.join(', ')}`;\n      badge.addEventListener('ed-remove', () => setFilters(grid.filters.filter(other => other.id !== filter.id)));\n      toolbar.append(badge);\n    }\n    if (grid.filters.length > 0) {\n      const reset = document.createElement('ed-button');\n      reset.slot = 'filters';\n      reset.setAttribute('appearance', 'plain');\n      reset.innerHTML = '<ed-icon slot=\"start\" name=\"close\"></ed-icon>Reset all filters';\n      reset.addEventListener('click', resetAll);\n      toolbar.append(reset);\n    }\n  }\n\n  function setFilters(filters) {\n    grid.filters = filters;\n    for (const field of fields) field.value = filters.find(filter => filter.id === field.dataset.column)?.value ?? [];\n    showState();\n  }\n\n  function resetAll() {\n    search.value = '';\n    grid.searchTerm = '';\n    setFilters([]);\n  }\n\n  for (const field of fields) {\n    field.addEventListener('change', () => {\n      const others = grid.filters.filter(filter => filter.id !== field.dataset.column);\n      grid.filters = field.value?.length ? [...others, { id: field.dataset.column, value: field.value }] : others;\n      showState();\n    });\n  }\n\n  search.addEventListener('input', () => {\n    grid.searchTerm = search.value;\n    showState();\n  });\n\n  grid.addEventListener('ed-data-grid-filter-change', event => setFilters(event.detail.filters));\n  document.getElementById('fi02-empty-reset').addEventListener('click', resetAll);\n\n  document.getElementById('fi02-filter').addEventListener('click', () => {\n    panel.hidden = !panel.hidden;\n    document.getElementById('fi02-filter-label').textContent = panel.hidden ? 'Filter' : 'Close filter';\n  });\n\n  setFilters([{ id: 'status', value: ['Open'] }]);\n</script>",
  "page": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/filter/fi02-inline-panel/",
  "topicPage": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/filter/"
}
