/*
 * Table
 *
 * Measured from Figma `Table Row` / `Table Cell` / `Cell Text` (173:42).
 * Replaces a pre-v2 file that predated this measurement: its header used
 * `font-weight: semibold` where Figma's header cell is Medium, and its three
 * density paddings were wrong on both axes — see DENSITY below.
 *
 * DENSITY ONLY MOVES VERTICAL PADDING
 *
 * Compact/Default/Relaxed are 8/16/20 top-and-bottom, but all three share the
 * SAME 16px horizontal padding. Columns do not get any narrower as density
 * increases — only row height does. A density system that also squeezed the
 * horizontal padding would have been a plausible-looking invention with
 * nothing in Figma behind it.
 *
 * HEADER FILL IS PER-CELL, NOT PER-ROW
 *
 * `surface/page` is bound on the header CELL, not on `<thead>`. Visually
 * identical when every header cell shares it, but it is why this reads
 * `.ion-table th` rather than a `.ion-table__header` row class — matching
 * where Figma actually put the token means a table that mixes header cells
 * into a body row (a sticky first column, say) still gets the fill from the
 * right place.
 *
 * ROW BORDER IS BOTTOM ONLY
 *
 * Figma's row stroke resolves to top 0 / right 0 / bottom 1 / left 0, not a
 * mixed/mystery value — confirmed by reading all four edges rather than
 * assuming "MIXED" meant "top and bottom". One `border-bottom`, not a
 * 4-sided border that needs collapsing.
 */

/*
 * `position: relative` makes the scroll container a containing block. Without
 * it, absolutely positioned content in a cell — the selection checkbox's real
 * input, `.ion-visually-hidden` text, avatar media — is placed against the
 * nearest positioned ancestor outside the table, escapes `overflow-x`, and
 * widens the page: 382px of sideways scroll on a 390px phone in the demo app.
 */
.ion-table-container {
  position: relative;
  width: 100%;
  overflow-x: auto;
  border: var(--border-width-default) solid var(--border-default);
  border-radius: var(--radius-md);
}

/* Keyboard focus ring for the scroll region — see Table.tsx. */
.ion-table-container:focus-visible {
  outline: var(--border-width-thick) solid var(--border-focus);
  outline-offset: 2px;
}

.ion-table {
  width: 100%;
  border-collapse: collapse;
  border-spacing: 0;
  font-family: var(--font-family-sans);
  font-size: var(--type-body-sm);
  line-height: var(--type-body-sm-line-height);
}

.ion-table th,
.ion-table td {
  padding: var(--spacing-16) var(--spacing-16);

  /*
   * On the cells, not only on the table. A <th> is centred by the browser
   * unless the table's own alignment is something other than the initial
   * value — and `start` IS the initial value, so `text-align: start` on the
   * table alone centred every header. `left` never had the problem, which is
   * how converting it to its logical form broke the headers in both
   * directions until the right-to-left stories measured one.
   */
  text-align: start;
  vertical-align: middle;
}

/*
 * Density — vertical padding only. See the header.
 *
 * Sits here, next to the padding it overrides, rather than further down with
 * the other modifiers: `.ion-table--compact td` is (0,1,1), lower than the
 * `.ion-table thead td` below it, so trailing it would read as descending
 * specificity. Grouping layout before appearance is the better order anyway.
 */
.ion-table--compact th,
.ion-table--compact td {
  padding-top: var(--spacing-8);
  padding-bottom: var(--spacing-8);
}

.ion-table--relaxed th,
.ion-table--relaxed td {
  padding-top: var(--spacing-20);
  padding-bottom: var(--spacing-20);
}

.ion-table td {
  color: var(--text-default);
  font-weight: var(--font-weight-regular);
}

/*
 * Header appearance follows EITHER condition: the cell is a `<th>`, or it sits
 * inside `<thead>`.
 *
 * The `<th>` half is the Figma binding (see the header). The `thead` half
 * exists for cells that sit in the header row but are not column headers —
 * rare, and mostly historical. Selection's select-all cell is a real `<th
 * scope="col">` with a labelled checkbox, so it gets the fill from the `<th>`
 * rule rather than this fallback.
 *
 * Declared after `.ion-table td` so specificity reads non-decreasing; the two
 * `td` selectors would otherwise trip `no-descending-specificity`.
 */
.ion-table th,
.ion-table thead td {
  background-color: var(--surface-page);
  color: var(--text-secondary);
  font-weight: var(--font-weight-medium);
  border-bottom: var(--border-width-default) solid var(--border-default);
}

.ion-table tr {
  background-color: var(--surface-default);
  transition: background-color var(--ion-duration-base) var(--ion-ease-out);
}

.ion-table tbody tr {
  border-bottom: var(--border-width-default) solid var(--border-default);
}

/*
 * Striped rows have to come BEFORE hover/selected in the file, and match
 * their specificity: with equal specificity the later rule wins, so a
 * striped row that is hovered or selected shows the tint, not the stripe.
 * Checked, not assumed: swapping the order silently shows the stripe.
 *
 * Counted among real rows only: an open row's detail is a second `<tr>`, and
 * `nth-of-type` would count it and shift every stripe below. `:nth-child(…
 * of .ion-table__row)` counts that class too, which is why the hover and
 * selected rules below name `.ion-table__row` — to stay level with it. The
 * detail takes its row's stripe and selection, so the pair reads as one.
 */
.ion-table--striped tbody tr:nth-child(even of .ion-table__row),
.ion-table--striped
  tbody
  tr:nth-child(even of .ion-table__row)
  + .ion-table__detail {
  background-color: var(--surface-page);
}

/* The detail is not a row to point at: hovering it tints nothing. */
@media (hover: hover) {
  .ion-table tbody tr.ion-table__row:hover {
    background-color: var(--surface-hover);
  }
}

.ion-table tbody tr.ion-table__row[data-hovered='true'] {
  background-color: var(--surface-hover);
}

.ion-table tbody tr.ion-table__row[data-selected='true'],
.ion-table tbody tr.ion-table__row[data-selected='true'] + .ion-table__detail {
  background-color: var(--surface-primary-subtle);
}

.ion-table th[data-align='trailing'],
.ion-table td[data-align='trailing'] {
  text-align: end;
}

.ion-table th[data-align='center'],
.ion-table td[data-align='center'] {
  text-align: center;
}

/* Cell content — icon, label, icon, matching Figma's `Cell Text`. */
.ion-table__cell-content {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-6);
}

.ion-table__cell-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--icon-interactive);
}

.ion-table__cell-icon--trailing {
  color: var(--icon-tertiary);
}

.ion-table__cell-icon svg {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
}

.ion-table th .ion-table__cell-icon--trailing {
  color: var(--icon-secondary);
}

/*
 * Sortable headers — `sortDirection` on a header TableCell, Figma's
 * `Table Sort Indicator` in the Cell Text trailing slot.
 *
 * The label is a real button inside the <th>, reset to read as header text:
 * same weight, same `text/secondary`, no chrome. It darkens to `text/default`
 * on hover and when it is the sorted column, so the current sort reads from
 * the label as well as the arrow. The unsorted indicator stays
 * `icon/tertiary`: present enough to say "this sorts", quiet enough that a
 * row of them is not noise.
 */
.ion-table__sort {
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-xs);
  background: none;
  color: inherit;
  font: inherit;
  text-align: inherit;

  /* The arrow beside a two-line label reads as belonging to one line of it.
     The table scrolls sideways before it squeezes a header this far. */
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--ion-duration-base) var(--ion-ease-out);
}

@media (hover: hover) {
  .ion-table__sort:hover {
    color: var(--text-default);
  }
}

.ion-table__sort:focus-visible {
  outline: var(--border-width-thick) solid var(--ring-focus);
  outline-offset: 2px;
}

.ion-table__sort--active {
  color: var(--text-default);
}

.ion-table th .ion-table__sort-icon {
  color: var(--icon-tertiary);
}

.ion-table th .ion-table__sort--active .ion-table__sort-icon {
  color: var(--icon-default);
}

/* Link cells — Figma's `Type=Link`. */
.ion-table__cell-content--link {
  color: var(--text-link);
}

/*
 * Column divider — Figma's per-cell `Show Divider`. A border-right rather
 * than the fixed-height rule Figma draws: Figma's rule is only ever as tall
 * as that one demo cell's text, which is an artifact of the canvas, not a
 * measurement to reproduce. A full-height border-right is the version that
 * still looks right at any row height.
 */
.ion-table th[data-divider='true'],
.ion-table td[data-divider='true'] {
  border-inline-end: var(--border-width-default) solid var(--border-default);
}

/*
 * Resizable columns — `resize` on a header TableCell, from `useTableColumns`.
 *
 * The handle is a strip on the header's trailing edge, inside the cell's
 * padding so it never overlaps the label: wide enough to hit with a finger,
 * with a rule drawn on the edge itself. The rule shows while the header is
 * hovered, the handle focused or dragged — and always where there is no
 * hover to find it with.
 *
 * `:where()` keeps `position: relative` below the sticky header's and held
 * column's `position: sticky`: both are containing blocks for the handle, so
 * a held header keeps its handle and stays held.
 */
.ion-table :where(th[data-resizable='true']) {
  position: relative;

  /* The width set is the column's, padding included: what a drag measures. */
  box-sizing: border-box;
}

.ion-table__resizer {
  position: absolute;
  top: 0;
  bottom: 0;
  inset-inline-end: 0;
  width: var(--spacing-16);
  cursor: col-resize;
  touch-action: none;
}

.ion-table__resizer::after {
  position: absolute;
  top: var(--spacing-8);
  bottom: var(--spacing-8);
  inset-inline-end: 0;
  width: var(--border-width-thick);
  border-radius: var(--radius-full);
  background-color: var(--border-strong);
  content: '';
  opacity: 0;
  transition: opacity var(--ion-duration-base) var(--ion-ease-out);
}

@media (hover: hover) {
  .ion-table th:hover > .ion-table__resizer::after {
    opacity: 1;
  }
}

@media (hover: none) {
  .ion-table__resizer::after {
    opacity: 1;
  }
}

.ion-table__resizer:focus-visible {
  outline: var(--border-width-thick) solid var(--ring-focus);
  outline-offset: calc(-1 * var(--border-width-thick));
}

.ion-table__resizer:focus-visible::after,
.ion-table__resizer[data-resizing='true']::after {
  background-color: var(--border-focus);
  opacity: 1;
}

/*
 * Sticky header and first column — `maxHeight` and `stickyFirstColumn` on
 * Table.
 *
 * BOTH STICK TO THE TABLE'S OWN SCROLL REGION. It scrolls sideways already,
 * and a sticky cell sticks to its nearest scroller, so the header cannot
 * stick to the page; `maxHeight` gives the region a height to scroll in.
 *
 * NOTHING FOCUSED HIDES UNDER THEM (WCAG 2.4.11). The region's scroll
 * padding is the header's height and the held column's width, both measured,
 * so a control tabbed to is scrolled clear of them rather than under them.
 *
 * No `overflow-y` here: `overflow-x: auto` already makes the region scroll
 * both ways.
 */
.ion-table-container--sticky-header {
  scroll-padding-top: var(--ion-table-head-height, 0);
}

.ion-table-container--sticky-column {
  scroll-padding-inline-start: var(--ion-table-held-width, 0);
}

/*
 * `border-collapse` draws borders on the table's grid, not the cell, so a
 * held header cell would leave its bottom rule behind as it scrolls. An
 * inset shadow is drawn by the cell and moves with it.
 */
.ion-table-container--sticky-header thead th,
.ion-table-container--sticky-header thead td {
  position: sticky;
  z-index: 2;
  top: 0;
  box-shadow: inset 0 calc(-1 * var(--border-width-default)) 0
    var(--border-default);
}

/*
 * A held cell takes its row's background — stripe, hover, selection — so the
 * cells scrolling under it are hidden, not shown through.
 */
.ion-table-container--sticky-column [data-sticky] {
  position: sticky;
  z-index: 1;
  inset-inline-start: var(--ion-table-sticky-left, 0);
  background-color: inherit;
}

/* The corner is held both ways, above both. */
.ion-table-container--sticky-column thead [data-sticky] {
  z-index: 3;
  background-color: var(--surface-page);
}

/* Once something has scrolled under the held column, its edge says so. */
.ion-table-container--sticky-column[data-scrolled-x='true'] [data-sticky-edge] {
  box-shadow: var(--ion-shadow-shadow-md);
  clip-path: inset(0 -16px 0 0);
}

/* The shadow falls past the column's end: the left, in right-to-left. */
.ion-table-container--sticky-column[data-scrolled-x='true']
  [data-sticky-edge]:dir(rtl) {
  clip-path: inset(0 0 0 -16px);
}

/*
 * Expandable rows — `expansion` on TableRow, Figma's `Show Expander`.
 *
 * The toggle column is as narrow as its button: `width: 1%` is the table
 * idiom for "shrink to content", and the cell loses its trailing padding so
 * the chevron sits against the next column's own.
 */
.ion-table th.ion-table__expander,
.ion-table td.ion-table__expander {
  width: 1%;
  padding-inline-end: 0;
}

/* A 24px target (WCAG 2.5.8), reset to read as part of the row. */
.ion-table__expander-button {
  display: inline-flex;
  width: var(--spacing-24);
  height: var(--spacing-24);
  margin: 0;
  padding: 0;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--radius-xs);
  background: none;
  color: var(--icon-secondary);
  cursor: pointer;
}

.ion-table__expander-button svg {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
  transition: transform var(--ion-duration-base) var(--ion-ease-out);
}

/* Open points down; closed points the reading direction — the shape says it,
   not only the name. Right-to-left turns it half round; the open rule below
   weighs the same and comes later, so open still wins. */
.ion-table__expander-button:dir(rtl) svg {
  transform: rotate(180deg);
}

.ion-table__expander-button[aria-expanded='true'] svg {
  transform: rotate(90deg);
}

@media (prefers-reduced-motion: reduce) {
  .ion-table__expander-button svg {
    transition: none;
  }
}

@media (hover: hover) {
  .ion-table__expander-button:hover {
    background-color: var(--surface-hover);
    color: var(--icon-default);
  }
}

.ion-table__expander-button:focus-visible {
  outline: var(--border-width-thick) solid var(--ring-focus);
  outline-offset: 2px;
}

/* The row and its detail read as one: no rule between them. */
.ion-table tbody tr[data-expanded='true'] {
  border-bottom-color: transparent;
}

/* The detail starts under the row's content, past the toggle. */
.ion-table .ion-table__detail > td {
  padding-top: 0;
  padding-inline-start: calc(var(--spacing-16) + var(--spacing-24));
}
