SchwarzIT / onyx

🚀 A design system and Vue.js component library created by Schwarz IT
https://onyx.schwarz
Apache License 2.0
55 stars 6 forks source link

Implement OnyxDataGridRenderer support component #1851

Open JoCa96 opened 1 week ago

JoCa96 commented 1 week ago

Why?

According to our #1660 we want to abstract the rendering of the OnyxDataGrid. We implement this TableRenderLayer via the OnyxDataGridRenderer support component.

It encompasses a minimal API that renders an HTML compliant table with only permitted content elements:

Permitted content

In this order:

  • an optional element,
  • zero or more elements,
  • an optional element,
  • either one of the following:
    • zero or more elements
    • one or more elements
  • an optional element

The TableRenderLayer must be stateless. Its main purpose is to create an HTML compliant table structure. The table should be rendered according to the following rules:

Design

Figma

Acceptance criteria

Definition of Done

Approval

Implementation details

The following is the code used by the PoC of #1660. @JoCa96 - I would consider making the id key dynamic. Column and row grouping, as well as styling was not yet considered.

import type { FunctionalComponent, HTMLAttributes, TdHTMLAttributes } from "vue";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyKey = keyof any;

export type TableEntry = {
  id: string | number;
  [key: AnyKey]: unknown;
};

export type Metadata = Record<string, unknown>;

export type CellRenderFunc<
  TEntry extends TableEntry,
  TMetadata extends Metadata,
> = FunctionalComponent<RenderCellProps<TEntry, TMetadata>>;

/**
 * Props of the TableRenderLayer
 */
export type RendererProps<TEntry extends TableEntry, TMetadata extends Metadata> = {
  theadProps?: HTMLAttributes;
  tbodyProps?: HTMLAttributes;
  columns: RenderHeader<TEntry>[];
  rows: RenderRow<TEntry, TMetadata>[];
};

export type RenderHeader<
  TEntry extends TableEntry,
  TKey extends keyof TEntry = keyof TEntry,
  TProps extends object = object,
> = {
  /**
   * Key of the column - usually a key of the tabledata.
   * But can also be used for custom columns.
   */
  key: TKey;
  /**
   * Attributes and data that is provided to the component using `v-bind`.
   */
  headerProps: HTMLAttributes & TProps;
  /**
   * The component that renders the header content.
   */
  header: FunctionalComponent<TProps>;
};

export type RenderRow<TEntry extends TableEntry, TMetadata extends Metadata> = {
  /**
   * Unique id of the row.
   */
  id: TableEntry["id"];
  trProps?: HTMLAttributes;
  cells: Record<keyof TEntry, RenderCell<TEntry, TMetadata> | undefined>;
};

export type RenderCell<TEntry extends TableEntry, TMetadata extends Metadata> = {
  props: RenderCellProps<TEntry, TMetadata>;
  tdProps?: TdHTMLAttributes;
  /**
   * The component that renders the actual cell content.
   */
  is: CellRenderFunc<TEntry, TMetadata>;
};

export type RenderCellProps<TEntry extends TableEntry, TMetadata extends Metadata> = {
  /**
   * Complete row data
   */
  row: TEntry;
  /**
   * Data that is provided to the component via the `metadata` prop
   */
  metadata?: TMetadata;
  /**
   * table data that is provided to the component via the `modelValue` prop
   */
  modelValue: TEntry[keyof TEntry];
};

Basic structure:

<template>
  <table>
    <thead v-bind="props.theadProps">
      <tr>
        <th v-for="col in props.columns" :key="col.key">
          <component :is="col.header" v-bind="col.headerProps" />
        </th>
      </tr>
    </thead>
    <tbody v-bind="props.tbodyProps">
      <tr v-for="row in props.rows" :key="row.id" v-bind="row.trProps">
        <td v-for="col in props.columns" :key="col.key" v-bind="row.cells[col.key].tdProps">
          <component :is="row.cells[col.key].is" v-bind="row.cells[col.key].props" />
        </td>
      </tr>
    </tbody>
  </table>
</template>

Applicable ARIA Pattern

larsrickert commented 9 hours ago

@JoCa96 Vacation handover: