umbral-admin 0.0.12

Auto-generated CRUD admin UI for umbral models.
Documentation
{#
  table.html
  ==========
  "Recent Orders / Recent Comments" style table widget — modeled
  on the user-supplied reference. The widget card itself (card
  bg, border, rounded corners) is already provided by the
  dashboard grid's per-widget wrapper. This macro fills that
  card with:

    • Compact title row + optional "View all →" link
    • Distinct header band (one notch darker than the card bg)
    • Row body in `bg-surface-container` (the card bg) — no
      zebra; visual rhythm comes from hairline dividers on
      `surface-container-highest`
    • First column heavier (`text-on-surface`, `font-medium`,
      `whitespace-nowrap`) — that's the row's "anchor" cell,
      matches the `<th scope="row">` pattern in the reference
    • Other columns softer (`text-on-surface-variant`)
    • Last row drops its bottom border

  Expects payload:
    {
      columns:      [{key, label}],
      rows:         [{key: value, ...}],
      view_all_url: Option<String>,
    }
#}

{% macro table_widget(title, payload) %}
<div class="table-widget h-full flex flex-col">
  {# Header: title (left) + View all link (right) ============== #}
  <div class="px-md pt-md pb-sm flex-shrink-0 flex items-center justify-between gap-sm">
    <p class="text-label-sm text-outline uppercase tracking-wider truncate">{{ title }}</p>
    {% if payload.view_all_url %}
    <a
      href="{{ payload.view_all_url }}"
      class="text-label-sm text-on-surface-variant hover:text-primary transition-colors inline-flex items-center gap-1 flex-shrink-0 no-underline"
    >
      View all
      <i data-lucide="arrow-up-right" class="w-3.5 h-3.5"></i>
    </a>
    {% endif %}
  </div>

  {% if payload.columns and payload.rows and payload.rows | length > 0 %}
  <div class="flex-1 min-h-0 px-md pb-md overflow-hidden">
    <div class="h-full overflow-auto rounded-xl border border-outline-variant bg-surface-container-low shadow-sm">
      <table class="w-full text-left border-collapse text-[15px] leading-snug text-on-surface-variant">
        {# Header — sticky, denser, and visually joined to the rounded
           table shell so the widget reads as an actual data table. #}
        <thead class="sticky top-0 z-10 bg-surface-container-high border-b border-outline-variant">
        <tr>
          {% for col in payload.columns %}
          <th
            scope="col"
            class="px-md py-sm font-label-md text-label-md text-on-surface-variant uppercase tracking-wider whitespace-nowrap"
          >{{ col.label }}</th>
          {% endfor %}
        </tr>
      </thead>
      <tbody class="divide-y divide-divider-soft dark:divide-gray-800/70">
        {% for row in payload.rows %}
        {# Alternating surface tokens give row rhythm without making the
           compact dashboard table feel grid-heavy. #}
        <tr class="{% if loop.index0 % 2 == 0 %}bg-surface-container{% else %}bg-surface-container-low{% endif %} hover:bg-surface-container-high transition-colors">
          {% for col in payload.columns %}
          {%- set v = row[col.key] -%}
          {%- set vstr = "" if v is none else (v | string) -%}
          {% if loop.first %}
          {# Row anchor cell — heavier weight, stronger color, no
             wrap. Matches the reference's `<th scope="row">`
             pattern (the leftmost column tells you which entity
             the row is about). #}
          <th
            scope="row"
            class="px-md py-sm font-medium text-on-surface whitespace-nowrap truncate max-w-[220px]"
            title="{{ vstr }}"
          >{{ vstr }}</th>
          {% else %}
          <td
            class="px-md py-sm truncate max-w-[200px] tabular-nums"
            title="{{ vstr }}"
          >{{ vstr }}</td>
          {% endif %}
          {% endfor %}
        </tr>
        {% endfor %}
      </tbody>
    </table>
    </div>
  </div>
  {% else %}
  <p class="text-label-sm text-outline italic px-md py-md mt-auto">No data</p>
  {% endif %}
</div>
{% endmacro %}