{
  "id": "TB01",
  "title": "Full toolbar",
  "topic": "toolbar",
  "use": "The fully populated toolbar above a table of vouchers. The search and the actions sit on the left, the most important button first. The fiscal year, the filter and the switch between views sit on the right, with the free space in between. The number of entries and the filters set stand in the row below.",
  "decisions": [
    "The search and the actions sit on the left, the filter, the fiscal year and the view switch on the right: what decides what is shown has its place at the trailing edge, and the toolbar's view zone keeps it there whatever stands on the left.",
    "The search takes the left edge, and the most important action comes first after it.",
    "The destructive action stands last, behind a divider. It is disabled without a selection and names the reason in its title instead of disappearing: a bar whose buttons come and go has to be read again with every selection.",
    "Exactly one accent button. The filled segment of the view switch is a state, not a second accent: it shows where you are and triggers nothing.",
    "The numbers stand below the bar, in the meta and filter rows — entry count, selection and the filters set; between the controls they could not be found.",
    "No label above a control: the year names itself in its value and the search in its placeholder, because a labeled select is three lines tall beside a one-line button.",
    "The fiscal year is a context choice, not a filter: the selection survives it, and the way out of an empty year is its value, not the filter reset."
  ],
  "try": [
    {
      "do": "Type “Tire” in the search field",
      "then": "the table gets shorter, and the count below follows"
    },
    {
      "do": "Press Filter",
      "then": "an example filter is set and appears as a badge in the row below"
    },
    {
      "do": "Press the cross on the badge",
      "then": "the filter goes, and the count stands alone again"
    },
    {
      "do": "Select two rows in the table",
      "then": "Delete vouchers becomes available, and the number selected stands in the row above the table"
    },
    {
      "do": "Select two rows, then search for “Spare parts”",
      "then": "the selection stays, and the row says how much of it is out of sight — “1 of 8 entries · 2 selected, 1 of them outside the view”. Delete vouchers stays available, since it applies to both. Take the search back — the selection is still there"
    },
    {
      "do": "Point at Delete vouchers while nothing is selected",
      "then": "the button is disabled and names the reason"
    },
    {
      "do": "Set the fiscal year to 2025",
      "then": "the table is empty, and the way out is the year, not the filter"
    },
    {
      "do": "Switch to Cards",
      "then": "the same vouchers as cards; the toolbar stays as it is"
    }
  ],
  "revision": 1,
  "changes": [],
  "reviewed": true,
  "source": "<style>\n  /* The cards scroll in a region of their own, as the table does. */\n  #tb01-cards {\n    overflow: auto;\n  }\n</style>\n\n<!-- Demo only, not part of the pattern. The table and the cards take the height the screen leaves them; here they are set to fit the frame. -->\n\n<div class=\"ed-stack ed-gap-4\">\n  <!-- Demo only, not part of the pattern. The screen's title comes from the screen around the pattern; SC01 places it. -->\n\n  <ed-toolbar id=\"tb01-toolbar\" label=\"Vouchers\">\n    <ed-input slot=\"search\" type=\"search\" placeholder=\"Voucher, posting text or type\" aria-label=\"Search vouchers\" style=\"inline-size: 18rem\"></ed-input>\n\n    <ed-button><ed-icon slot=\"start\" name=\"plus\"></ed-icon>Create voucher</ed-button>\n    <ed-button appearance=\"plain\"><ed-icon slot=\"start\" name=\"export\"></ed-icon>Export vouchers</ed-button>\n    <ed-divider orientation=\"vertical\"></ed-divider>\n    <ed-button id=\"tb01-delete\" appearance=\"plain\" disabled title=\"Select at least one voucher\"><ed-icon slot=\"start\" name=\"delete\"></ed-icon>Delete vouchers</ed-button>\n\n    <ed-select id=\"tb01-year\" slot=\"view\" aria-label=\"Fiscal year\" value=\"2026\">\n      <ed-option value=\"2026\">Fiscal year 2026</ed-option>\n      <ed-option value=\"2025\">Fiscal year 2025</ed-option>\n    </ed-select>\n    <ed-button id=\"tb01-filter\" slot=\"view\" appearance=\"plain\"><ed-icon slot=\"start\" name=\"filter\"></ed-icon>Filter</ed-button>\n    <ed-radio-group id=\"tb01-view\" slot=\"view\" appearance=\"button\" aria-label=\"View\" value=\"table\">\n      <ed-radio value=\"table\"><ed-icon slot=\"start\" name=\"table\"></ed-icon>Table</ed-radio>\n      <ed-radio value=\"cards\"><ed-icon slot=\"start\" name=\"grid\"></ed-icon>Cards</ed-radio>\n    </ed-radio-group>\n\n    <span id=\"tb01-count\" slot=\"meta\" role=\"status\" tabindex=\"-1\"></span>\n  </ed-toolbar>\n\n  <ed-data-grid id=\"tb01-grid\" label=\"Vouchers\" without-toolbar selectable selection-mode=\"multiple\" data-ed-preload=\"ed-format-date\">\n    <ed-empty-state slot=\"empty\" size=\"small\" icon=\"calendar\">\n      <span slot=\"label\">No voucher in fiscal year 2025</span>\n      The fiscal year is a context choice, not a filter — the way back is its value.\n      <ed-button id=\"tb01-current-year\" slot=\"button\" appearance=\"outlined\">Select fiscal year 2026</ed-button>\n    </ed-empty-state>\n    <ed-empty-state slot=\"no-results\" size=\"small\" icon=\"search\">\n      <span id=\"tb01-none-label\" slot=\"label\"></span>\n      <span id=\"tb01-none-text\"></span>\n      <ed-button id=\"tb01-clear\" slot=\"button\" appearance=\"outlined\"></ed-button>\n    </ed-empty-state>\n  </ed-data-grid>\n\n  <div id=\"tb01-cards\" hidden>\n    <ul class=\"ed-grid ed-list-plain ed-m-0\" style=\"--ed-grid-min-column-size: 15rem\"></ul>\n  </div>\n</div>\n\n<!-- Demo only, not part of the pattern. The product supplies vouchersOf(year): the vouchers of one fiscal year, as { id, date, text, type, status }, with date as YYYY-MM-DD. -->\n\n<!-- Demo only, not part of the pattern. The Filter button opens the product's filter surface, the drawer of FI01 or the panel of FI02, whose fields set grid.filters; here it toggles exampleFilter, one filter on the type column, so the row below has a badge to show. -->\n\n<script type=\"module\">\n  const toolbar = document.getElementById('tb01-toolbar');\n  const search = toolbar.querySelector('ed-input');\n  const year = document.getElementById('tb01-year');\n  const view = document.getElementById('tb01-view');\n  const remove = document.getElementById('tb01-delete');\n  const count = document.getElementById('tb01-count');\n  const grid = document.getElementById('tb01-grid');\n  const cards = document.getElementById('tb01-cards');\n  const noneLabel = document.getElementById('tb01-none-label');\n  const noneText = document.getElementById('tb01-none-text');\n  const clearButton = document.getElementById('tb01-clear');\n\n  const statusVariant = { Open: 'neutral', Error: 'danger', Reviewed: 'info', Finalized: 'success' };\n\n  // Built from elements, not from an HTML string: a voucher's text is data and never becomes markup.\n  const element = (tag, properties) => Object.assign(document.createElement(tag), properties);\n  const day = value => element('ed-format-date', { date: value, month: 'short', day: 'numeric', year: 'numeric' });\n  const card = row => {\n    const badge = element('ed-badge', { slot: 'header-end', textContent: row.status });\n    badge.setAttribute('variant', statusVariant[row.status]);\n    const body = element('div', { className: 'ed-stack ed-gap-1' });\n    const caption = element('p', { className: 'ed-caption-m' });\n    caption.append(day(row.date), ` · ${row.type}`);\n    body.append(element('p', { textContent: row.text }), caption);\n    const box = element('ed-card');\n    box.append(element('h3', { slot: 'header', textContent: row.id }), badge, body);\n    const item = element('li');\n    item.append(box);\n    return item;\n  };\n\n  // The search covers what the empty state names: number, posting text and type.\n  const columns = [\n    { field: 'id', label: 'Voucher', width: 140, rowheader: true },\n    { field: 'date', label: 'Date', width: 130, formatter: day, searchable: false },\n    { field: 'text', label: 'Posting text', flex: 2, minWidth: 220 },\n    { field: 'type', label: 'Type', width: 170, filterable: true, filterType: 'set' },\n    {\n      field: 'status',\n      label: 'Status',\n      width: 130,\n      searchable: false,\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\n  grid.columns = columns;\n  // The attribute, not `value`: the select may not be defined yet when this runs.\n  grid.data = vouchersOf(year.getAttribute('value'));\n\n  const entries = number => (number === 1 ? '1 entry' : `${number} entries`);\n\n  const update = async () => {\n    await grid.updateComplete;\n    const term = grid.searchTerm.trim();\n    const rows = grid.getProcessedRows();\n    const selected = grid.selectedKeys.length;\n    const outside = grid.selectedKeys.filter(key => !rows.some(row => row.id === key)).length;\n    count.textContent = [\n      grid.filteredCount === grid.data.length ? entries(grid.data.length) : `${grid.filteredCount} of ${entries(grid.data.length)}`,\n      selected ? `${selected} selected${outside ? `, ${outside} of them outside the view` : ''}` : ''\n    ].filter(Boolean).join(' · ');\n\n    toolbar.querySelectorAll('[slot=\"filters\"]').forEach(element => element.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 = `${columns.find(column => column.field === filter.id).label}: ${[filter.value].flat().join(', ')}`;\n      badge.addEventListener('ed-remove', () => {\n        grid.filters = grid.filters.filter(other => other.id !== filter.id);\n        update().then(() => count.focus());\n      });\n      toolbar.append(badge);\n    }\n    if (grid.filters.length || term) {\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', () => clear().then(() => count.focus()));\n      toolbar.append(reset);\n    }\n\n    // The empty state names what emptied the table: the search, the filter, or both.\n    const types = grid.filters.flatMap(filter => filter.value).join(', ');\n    const [label, text, action] = term && grid.filters.length\n      ? [`No match for “${term}”`, `Only vouchers of type ${types} are searched.`, 'Reset all filters']\n      : term\n        ? [`No match for “${term}”`, 'No voucher contains this text in its number, posting text or type.', 'Clear search']\n        : ['No voucher matches the active filters', `Set: Type ${types}.`, 'Reset all filters'];\n    noneLabel.textContent = label;\n    noneText.textContent = text;\n    clearButton.textContent = action;\n\n    remove.disabled = selected === 0;\n    remove.title = selected ? '' : 'Select at least one voucher';\n\n    const asCards = view.value === 'cards' && rows.length > 0;\n    grid.hidden = asCards;\n    cards.hidden = !asCards;\n    cards.firstElementChild.replaceChildren(...rows.map(card));\n  };\n\n  const clear = () => {\n    grid.filters = [];\n    grid.searchTerm = '';\n    search.value = '';\n    return update();\n  };\n\n  // The fiscal year is a context choice, not a filter: the selection survives it.\n  const showYear = value => {\n    year.value = value;\n    grid.data = vouchersOf(value);\n    return update();\n  };\n\n  search.addEventListener('input', () => {\n    grid.searchTerm = search.value;\n    update();\n  });\n  document.getElementById('tb01-filter').addEventListener('click', () => {\n    grid.filters = grid.filters.length ? [] : [exampleFilter];\n    update();\n  });\n  year.addEventListener('change', () => showYear(year.value));\n  view.addEventListener('change', update);\n  // The empty state leaves with the rows its button brings back; the count takes the focus.\n  document.getElementById('tb01-current-year').addEventListener('click', () => showYear('2026').then(() => count.focus()));\n  clearButton.addEventListener('click', () => clear().then(() => count.focus()));\n  for (const type of ['ed-data-grid-filter-change', 'ed-data-grid-sort-change', 'ed-data-grid-row-select']) {\n    grid.addEventListener(type, update);\n  }\n\n  await Promise.all(['ed-data-grid', 'ed-input', 'ed-radio-group'].map(tag => customElements.whenDefined(tag)));\n  update();\n</script>",
  "page": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/toolbar/tb01-full-toolbar/",
  "topicPage": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/toolbar/"
}
