wbDesign Wiki
Storybook
Angularorganism
ПримерОткрыть в Storybook ↗

Пример из опубликованного Storybook. Версия может отличаться от текущего контракта ниже.

Level: organism.

Purpose and use

Compose a desktop record table with filters, typed cells, selection, actions and pagination. Supply complete fixture/data inputs; Table does not call backend APIs.

Anatomy, behavior, and accessibility

  • Optional Filter Row and bulk-action bar surround a horizontally scrollable table with Header/Cell components; optional Pagination follows it. Column IDs address each row's cells; row IDs must be stable and unique.

  • Table forwards Filter Row's controlled filterOpenRequest and semantic filterAction without changing their payloads. The enclosing screen owns filter values, overlay state, and local or remote data filtering.

  • With containedScroll=true, Table fills its available height and makes only the scroll frame vertically and horizontally scrollable. The Filter Row stays outside that viewport and the column header remains sticky at its top edge. The viewport exposes role="region", tabindex="0", and tableAriaLabel.

  • Sort requests emit column/direction and a local flag. Rows are sorted locally when pagination is hidden or totalItems does not exceed the supplied rows; otherwise the parent provides ordered pages. Pagination emits changes without slicing rows. Replacing the columns array resets internal sort override.

  • Enabled checkbox cells update internal selection and aggregate header state. Replacing the rows array resets internal selection overrides. Bulk buttons emit selectedRows then clear selection; bulkActions=false removes checkbox columns.

  • Fixed edge columns receive a fixedOffset calculated from the widths of preceding fixed columns and are anchored natively through inset-inline-start/inset-inline-end. No scroll-driven transform or signal updates their position. Table passes the boundary side through Header's sticky input; Header owns its bottom and boundary separators. Loading marks busy, overlays a progress indicator and disables selection/action paths. filteredEmpty with zero rows shows filter-result copy only when loading is false.

  • fixedColumn: true on the first data column and on consecutive following columns creates one leading fixed group. The group ends at the first column without explicit true; Table derives cumulative left offsets and applies the boundary separator to its final column.

  • WblTableColumn.fixedColumn: false explicitly removes that column from automatic edge fixation. A first data column or actions column with this value receives no fixed side, inset offset, fixed divider, or Table Cell fixed boundary. Omitting fixedColumn preserves the current automatic first-data/actions behavior for existing consumers.

  • When Table is projected into wbl-main-container, its Filter Row, any rendered Action Bar, and scroll frame/grid remain inside the same page-layout-selected rail. Main Container never creates a Table breakout; the viewport, including any native scrollbar gutter, uses exactly the rail's inline edges. containedScroll and pagination never widen it to reserve that gutter. The grid keeps ownership of horizontal scrolling and fixed edges. This is a Main Container composition rule, not a Table input, variant, or public API.

  • Table, rows, headers and cells expose structural roles and accessible names; interactive descendants provide native buttons/links/checkboxes. Use column, row-selection and action labels meaningful without surrounding visual context.

  • With adaptive=true, Table measures its own inline size (not the window). Below 1024px it replaces the grid with a list of cards, one per row; at 1024px and wider it stays a table. adaptive defaults to false, so existing consumers are unchanged. Each card starts with a header row: the selection checkbox (when bulk actions are on) at the start and a «more» (three dots) menu at the end. Below it come «label — value» pairs for the first cardPrimaryColumns data columns (default 5); labels use --color-text-icon-primary-gray. The menu holds the row actions; selecting one emits the usual actionSelect. When there are more data columns, a «Показать ещё» button at the bottom of the card reveals them inside the same card and becomes «Свернуть». The block expands and collapses smoothly (height and opacity); with prefers-reduced-motion: reduce it switches instantly. Collapsed fields stay in the DOM but are inert. In card mode the action bar stays on one line with 8px block padding: «Выбрано» at the start, bulk buttons at the end, rendered as primary flat buttons. Once rows are selected, «Выбрать все N строк» takes a second line under «Выбрано» so it never overlaps the buttons. Cards stack in one full-width column with no border or radius and 12px inline padding, matching the action bar; a divider separates neighbouring cards. Number cells are left-aligned in cards; fixed columns and horizontal scroll do not apply. Cards keep role="table"/row, and each label is a rowheader.

Open Questions

Adaptive cards (A-011, prototype under review): there is no column header, so sorting and the header «select all» checkbox are unavailable in card mode (selecting all remains possible from the action bar after the first selection). Chevron columns are omitted from cards. The 1024px threshold is a component constant, not a token: a shared breakpoint scale (390 / 780 / 1024 / 1280 / 1440 / 1920) is proposed but not yet recorded in specs/ux/foundations or tokens.scss. Filter Row and Pagination have no adaptive behaviour yet. Row action variants are not distinguished inside the card menu. No Figma evidence exists for the card layout.

There is no virtual scrolling, backend sorting/paging or full grid keyboard navigation. A chevron event does not itself render nested rows. Empty rows without filteredEmpty have no dedicated empty illustration; screen-level state selection and data-error messaging remain caller responsibilities.

Local component contract

This is the active owned contract for component.table. The API below is verified against the current local implementation, focused test and story. The Source commit 0112cba0779a2d2a4b6c7a17243201694344d70a remains migration provenance, not the runtime owner.

The Main Container composition rule above does not add a Table input, output, model, selector, type, or standalone variant.

The fixedColumn: false behavior described above is the current M-002 semantic extension of the existing WblTableColumn.fixedColumn field. Focused tests verify the opt-out while omitted values preserve automatic edge fixation.

Classes and selectors

  • WblTableComponent — wbl-table (src/app/design-system/organisms/table/table.component.ts).

Inputs

  • WblTableComponent.columns = input<WblTableColumn[]>(DEFAULT_COLUMNS)

  • WblTableComponent.rows = input<WblTableRow[]>(DEFAULT_ROWS)

  • WblTableComponent.ariaLabel = input('Таблица')

  • WblTableComponent.tableAriaLabel = input('Данные таблицы')

  • WblTableComponent.bulkActions = input(true)

  • WblTableComponent.loading = input(false)

  • WblTableComponent.containedScroll = input(false)

  • WblTableComponent.adaptive = input(false)

  • WblTableComponent.cardPrimaryColumns = input(5)

  • WblTableComponent.cardExpandLabel = input('Показать ещё')

  • WblTableComponent.cardCollapseLabel = input('Свернуть')

  • WblTableComponent.cardMenuLabel = input('Действия')

  • WblTableComponent.filteredEmpty = input(false)

  • WblTableComponent.filteredEmptyEntityName = input('Записи')

  • WblTableComponent.actionBarButton2 = input(true)

  • WblTableComponent.actionBarButton3 = input(true)

  • WblTableComponent.actionBarButton1Label = input('Отменить')

  • WblTableComponent.actionBarButton2Label = input('Скачать')

  • WblTableComponent.actionBarButton3Label = input('Подтвердить')

  • WblTableComponent.showFilterRow = input(true)

  • WblTableComponent.filters = input<WblFilterRowFilter[]>([])

  • WblTableComponent.addFilterItems = input<WblDropdownItem[]>([])

  • WblTableComponent.showSearch = input(true)

  • WblTableComponent.searchLabel = input('Поиск')

  • WblTableComponent.searchPlaceholder = input('Поиск')

  • WblTableComponent.searchValue = input('')

  • WblTableComponent.showAddFilter = input(true)

  • WblTableComponent.addFilterLabel = input('Фильтр')

  • WblTableComponent.showReset = input(true)

  • WblTableComponent.resetDisabled = input<boolean | null>(null)

  • WblTableComponent.showSwitchColumn = input(true)

  • WblTableComponent.showSecondSwitch = input(false)

  • WblTableComponent.switches = input<WblFilterRowSwitch[]>([])

  • WblTableComponent.showColumns = input(true)

  • WblTableComponent.columnsLabel = input('Настроить колонки')

  • WblTableComponent.showActions = input(true)

  • WblTableComponent.showMoreAction = input(true)

  • WblTableComponent.downloadLabel = input('Скачать')

  • WblTableComponent.moreLabel = input('Ещё')

  • WblTableComponent.moreActionItems = input<WblDropdownItem[]>([])

  • WblTableComponent.downloadDisabled = input(false)

  • WblTableComponent.moreDisabled = input(false)

  • WblTableComponent.filterAriaLabel = input('Фильтры таблицы')

  • WblTableComponent.showPagination = input(true)

  • WblTableComponent.page = input(1)

  • WblTableComponent.pageSize = input(25)

  • WblTableComponent.totalItems = input(0)

  • WblTableComponent.pageSizeOptions = input<number[]>([25, 50, 100])

  • WblTableComponent.rowsPerPageLabel = input('Строк на странице')

  • WblTableComponent.paginationAriaLabel = input('Пагинация таблицы')

  • WblTableComponent.paginationDisabled = input(false)

  • WblTableComponent.filteredEmptyTitleText = input<string | null>(null)

  • WblTableComponent.filteredEmptyCaptionText = input<string | null>(null)

Each nullable input overrides only the matching filtered-empty line. null preserves the current copy derived from filteredEmptyEntityName and showSearch. Transport Requests passes a tab-specific title and the shared caption «Попробуйте изменить фильтры». Focused tests and the FilteredEmpty Storybook state are current evidence.

Public types and allowed values

type WblTableColumn = {
  id: string;
  label: string;
  width?: string;
  alignment?: WblTableHeaderAlignment;
  variant?: WblTableHeaderVariant;
  fixedColumn?: boolean;
  infoIcon?: boolean;
  sortable?: boolean;
  sortDirection?: WblTableHeaderSortDirection;
  checked?: boolean;
  intermediate?: boolean;
  ariaLabel?: string;
  checkboxLabel?: string;
};
type WblTableRow = {
  id: string;
  cells: Record<string, WblTableCell>;
  ariaLabel?: string;
};
type WblFilterRowFilter = {
  id: string;
  label: string;
  value?: string;
  filled?: boolean;
  multiple?: boolean;
  count?: string | number;
  lifecycle: WblFilterLifecycle;
  opened: boolean;
  invalid?: boolean;
  errorText?: string;
  disabled?: boolean;
};
type WblFilterLifecycle = 'required' | 'pinned' | 'optional';
type WblFilterAction = 'clear' | 'reset' | 'remove';
type WblFilterRowOpenRequest = {
  filter: WblFilterRowFilter;
  opened: boolean;
};
type WblFilterRowFilterAction = {
  filter: WblFilterRowFilter;
  action: WblFilterAction;
};
type WblDropdownItem = {
  id: string;
  label: string;
  iconName?: WblIconName;
  imageSrc?: string;
  imageAlt?: string;
  badge?: string;
  rightText?: string;
  selected?: boolean;
  disabled?: boolean;
  separatorBefore?: boolean;
};
type WblFilterRowSwitch = {
  id: string;
  label: string;
  selected?: boolean;
  disabled?: boolean;
};
type WblTableSortChange = {
  column: WblTableColumn;
  direction: WblTableHeaderSortDirection;
  local: boolean;
};
type WblTableHeaderCheckedChange = {
  column: WblTableColumn;
  checked: boolean;
};
type WblTableCellCheckedChange = {
  row: WblTableRow;
  column: WblTableColumn;
  checked: boolean;
};
type WblTableRowOpenedChange = {
  row: WblTableRow;
  column: WblTableColumn;
  opened: boolean;
};
type WblTableActionSelect = {
  row: WblTableRow;
  column: WblTableColumn;
  action: WblTableCellAction;
};
type WblTableActionBarButtonClick = {
  button: WblTableActionBarButton;
  selectedRows: WblTableRow[];
};
type WblFilterRowSwitchChange = {
  switchItem: WblFilterRowSwitch;
  selected: boolean;
};
interface WblPaginationChange {
  page: number;
  pageSize: number;
}
type WblTableHeaderAlignment = 'left' | 'right';
type WblTableHeaderVariant = 'text' | 'checkbox';
type WblTableHeaderSortDirection = 'none' | 'ascending' | 'descending';
type WblTableCell = {
  type?: WblTableCellType;
  fixed?: WblTableCellFixed;
  link?: WblTableCellLink;
  alignment?: WblTableCellAlignment;
  href?: string;
  leftIcon?: boolean;
  leftIconName?: WblIconName;
  text?: string;
  description?: boolean;
  descriptionText?: string;
  rightIcon?: boolean;
  rightIconName?: WblIconName;
  rightIconPlacement?: WblTableCellRightIconPlacement;
  hint?: boolean;
  hintLabel?: string;
  badgeText?: string;
  badgeAppearance?: WblBadgeAppearance;
  checked?: boolean;
  intermediate?: boolean;
  disable?: boolean;
  checkboxLabel?: string;
  opened?: boolean;
  expandLabel?: string;
  collapseLabel?: string;
  actions?: WblTableCellAction[];
  moreActionLabel?: string;
};
type WblTableCellAction = {
  id: string;
  label: string;
  variant?: WblTableCellActionButtonVariant;
  iconName?: WblIconName;
  showLabel?: boolean;
  tooltipText?: string;
  disabled?: boolean;
  overflow?: boolean;
  separatorBefore?: boolean;
};
type WblTableActionBarButton = 'button1' | 'button2' | 'button3';
type WblTableCellType =
  | 'none'
  | 'text'
  | 'number'
  | 'link'
  | 'badge'
  | 'checkbox'
  | 'chevron'
  | 'slot'
  | 'actions';
type WblTableCellFixed = 'none' | 'left' | 'right';
type WblTableCellLink = 'accent' | 'secondary';
type WblTableCellAlignment = 'left' | 'right';
type WblBadgeAppearance =
  | 'default'
  | 'red'
  | 'orange'
  | 'green'
  | 'success'
  | 'warning'
  | 'error'
  | 'clear'
  | 'disabled'
  | 'info';
type WblTableCellActionButtonVariant = WblButtonVariant | `flat-${WblButtonFlatVariant}`;
type WblButtonVariant = 'primary' | 'secondary' | 'danger' | 'inverted';
type WblButtonFlatVariant = 'primary' | 'secondary' | 'warning' | 'success' | 'danger';

Icon-name inputs use WblIconName from the shared icon contract; choose a key present in the owned icon pack, not an arbitrary external icon name.

Contained rows viewport

Transport Requests passes [containedScroll]="true" to constrain the rows region to the remaining vertical space. Filter Row stays outside that vertical scrollport, the column header remains sticky at its top edge, and the screen's infinite-scroll listener uses this viewport. Existing Table consumers keep the unbounded vertical behavior through the false default. The current implementation, focused tests, and ContainedScroll Storybook story verify the current contract.

Models

  • None.

Outputs

  • WblTableComponent.sortChange = output<WblTableSortChange>()
  • WblTableComponent.headerCheckedChange = output<WblTableHeaderCheckedChange>()
  • WblTableComponent.cellCheckedChange = output<WblTableCellCheckedChange>()
  • WblTableComponent.rowOpenedChange = output<WblTableRowOpenedChange>()
  • WblTableComponent.actionSelect = output<WblTableActionSelect>()
  • WblTableComponent.actionBarButtonClick = output<WblTableActionBarButtonClick>()
  • WblTableComponent.searchValueChange = output<string>()
  • WblTableComponent.filterOpenRequest = output<WblFilterRowOpenRequest>()
  • WblTableComponent.filterAction = output<WblFilterRowFilterAction>()
  • WblTableComponent.addFilterSelect = output<WblDropdownItem>()
  • WblTableComponent.resetClick = output<void>()
  • WblTableComponent.switchChange = output<WblFilterRowSwitchChange>()
  • WblTableComponent.columnsClick = output<void>()
  • WblTableComponent.downloadClick = output<void>()
  • WblTableComponent.moreActionSelect = output<WblDropdownItem>()
  • WblTableComponent.pageChange = output<WblPaginationChange>()
  • WblTableComponent.pageSizeChange = output<number>()

Slots and projection markers

  • WblTableComponent: <ng-content select="[wblFilterRowControl]">

Local evidence

  • Code: src/app/design-system/organisms/table/table.component.ts.
  • Focused test: src/app/design-system/organisms/table/table.component.spec.ts.
  • Public export: export * from './organisms/table/table.component'; in src/app/design-system/index.ts.
  • Storybook: storybook/stories/table.stories.ts (Design System/Organisms/wbl-table, including ContainedScroll).
  • Adaptive story: storybook/stories/adaptive-table.stories.ts (Адаптивность/Таблица), a container-width switcher for 390–1920px. 390, 780 and 1024 render inside a device mock-up taken from UI Kit node 12101:104965: status bar, logo with menu icon, and a 60px system area at the bottom. Desktop widths show a 48px menu placeholder. Every width shows a adaptive Header Widget and a non-interactive «Фильтры» button above the Table.

Figma status

This runtime contract does not infer a Figma owner. Assembly evidence, when verified, is maintained separately through the inventory-linked sidecar.

Provenance

The archived source-document copy is historical evidence only. The active resolver is local code/test/export and the exact Storybook story above.

M-002 scrollbar refinement

The Table scroll frame uses the owned custom scrollbar treatment evidenced by UI Kit node 9036:58781 for both contained vertical overflow and horizontal overflow. The native scrollbar is 4px, uses tertiary #c4c4d4 for the pill-radius thumb, keeps the track transparent, and replaces the former decorative rail. Styling does not change the viewport's accessible region, focusability, sticky header, column widths, or scroll ownership. The public API is unchanged.

M-002 contained scrollbar within the rail

In contained-scroll mode, the scroll frame is a flex container with overflow: visible and no inline-end padding. The viewport uses inline-size: 100%, overflow: auto, and native scrollbar-gutter: stable. Any browser-reserved gutter stays inside that rail-bounded viewport; it never widens the Table or creates outside layout area. The existing 4px thumb treatment, native scroll ownership, public API, focused tests, and ContainedScroll story remain current evidence.

M-002 per-tab fixed mapping and content transition

Transport Requests passes a separate column definition for each tab. The «Предварительные» columns opt out of fixed edges. The other four tabs mark their first two columns (ID / статус and Тип заявки) fixedColumn: true, producing a consecutive leading fixed group, and mark their final actions column fixedColumn: true for the right edge. Table derives the sides, cumulative offsets and boundary separators from that configuration and does not contain screen-specific column IDs or defaults.

The screen-owned wrapper, rather than Table, animates content after a selected tab changes: opacity: 0.9 transitions to the normal state over --duration-fast with --ease-standard. It does not animate Filter Row or the sticky column header, change Table geometry, or block input; the screen resets horizontal scroll to the start on each tab change. With prefers-reduced-motion: reduce, the animation is disabled and content is replaced immediately. The Table public API is unchanged.

M-002 sorting and contained scrollbar

Transport Requests marks ID, creation date and vehicle-submission date sortable in its four non-preliminary working/archive tabs. ID keeps the existing localeCompare path with numeric: true. Table recognizes DD.MM.YYYY HH:mm cell values and compares their timestamps chronologically; other values retain the existing numeric/string fallbacks. Creation date starts descending. This behavior adds no public input.

In contained-scroll and paginated layouts, .wbl-table__scroll-frame uses flex, overflow: visible, and no inline-end padding. The viewport keeps inline-size: 100%, overflow: auto, and scrollbar-gutter: stable. The native vertical scrollbar is reserved inside the rail-bounded viewport, so its outer inline edges still match the Table, Filter Row, and any Action Bar. Vertical and horizontal scrolling remain on the same accessible viewport. The public API is unchanged.

M-002 fixed columns and stable state geometry

Configured leading and trailing columns remain visually stationary while the middle data region scrolls horizontally. Table calculates each fixedOffset from the widths of preceding fixed columns and anchors the group with native inline-start/inline-end insets. It does not subscribe to scrollLeft or drive transforms/signals. Fixed surfaces retain opaque backgrounds and one boundary separator without covering scrolling cells. Browser verification at scrollLeft 0 and 1200 keeps the left columns at x=92–228 and x=228–452 and the right column at x=1177–1249. This behavior uses the existing fixedColumn contract and adds no public API.

.wbl-table__inner, the header grid and row grids retain their actual max-content width. A feature-level minimum-width override is not required; Transport Requests removed its former 1316px override. Its screen-owned Preliminary class sets Table inner, header and rows to inline-size/min-inline-size: 100%; with a minmax(376px, 1fr) route and 68px action column, the table fills the 1156px rail without an empty area or unnecessary horizontal overflow. Extended tabs retain their real max-content width.

Changing tabs, roles, screen state or row-action availability must preserve the Table viewport size, fixed-column positions, column widths and action-column footprint. In filtered-empty, the viewport uses hidden overflow, renders no scrollbar or divider, and keeps its title/caption. Per-tab loading, empty, error and filtered-empty selection remains a screen responsibility; Table does not infer authorization or role state.

WB Logistics · Design SystemСборка 91da5ae

Поиск по Wiki

Начните вводить название или текст