Table Header

Header cell for tabular data.

Installation

$npx @309-thingspire/ui@latest add table-header

Usage

import { TableHeader } from "@/components/table-header/table-header"
<TableHeader />

Examples

Live preview rendered from TableHeader.preview.tsx. Switch to the Code tab to view the underlying component source.

Loading preview…
import React from 'react';

import { border, colors, spacing, typography } from '../../style-tokens';

import { Checkbox } from '../Checkbox/Checkbox';

import type { TableHeaderDirection, TableHeaderProps } from './TableHeader.types';

const palette = colors.primitive.palette;
const textBase = colors.semantic.theme.text.base;

const HEADER_WIDTH = spacing.scale['224'] + spacing.scale['56'];

function toTypographyStyle(token: {
  fontFamily: string;
  fontSize: number;
  fontWeight: number;
  lineHeight: number;
  letterSpacing: number;
}) {
  return {
    fontFamily: token.fontFamily,
    fontSize: token.fontSize,
    fontWeight: token.fontWeight,
    lineHeight: `${token.lineHeight}px`,
    letterSpacing: `${token.letterSpacing}px`,
  };
}

function SortIcon({ size, color }: { size: number; color: string }) {
  return (
    <svg aria-hidden="true" viewBox="0 0 20 20" style={{ width: size, height: size, display: 'block' }}>
      <path
        d="M6.5 8L10 4.5L13.5 8"
        fill="none"
        stroke={color}
        strokeWidth={border.width['2']}
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M6.5 12L10 15.5L13.5 12"
        fill="none"
        stroke={color}
        strokeWidth={border.width['2']}
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

function getTextContainerStyle(direction: TableHeaderDirection): React.CSSProperties {
  if (direction === 'center') {
    return {
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      gap: spacing.scale['2'],
      flex: '1 0 0',
      minWidth: spacing.scale['0'],
      minHeight: spacing.scale['0'],
    };
  }

  if (direction === 'right') {
    return {
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'flex-end',
      gap: spacing.scale['2'],
      flex: '1 0 0',
      minWidth: spacing.scale['0'],
      minHeight: spacing.scale['0'],
    };
  }

  return {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'flex-start',
    gap: spacing.scale['2'],
    flexShrink: spacing.scale['0'],
  };
}

export function TableHeader({
  id,
  className,
  style,
  direction = 'left',
  disabled = false,
  title = 'Header',
  showCheckbox = true,
  checkboxChecked = false,
  showSortIcon = false,
  onCheckboxCheckedChange,
  onSortClick,
}: TableHeaderProps) {
  return (
    <div
      id={id}
      className={className}
      style={{
        width: HEADER_WIDTH,
        height: spacing.scale['40'],
        display: 'flex',
        alignItems: 'center',
        gap: spacing.scale['12'],
        paddingInline: spacing.scale['12'],
        backgroundColor: palette.gray['1'],
        boxSizing: 'border-box',
        opacity: disabled ? 0.6 : 1,
        ...style,
      }}
      aria-disabled={disabled || undefined}
    >
      {showCheckbox ? (
        <span
          style={{
            width: spacing.scale['16'],
            height: spacing.scale['16'],
            display: 'inline-flex',
            alignItems: 'center',
            justifyContent: 'center',
            flexShrink: spacing.scale['0'],
          }}
        >
          <Checkbox
            size="sm"
            checked={checkboxChecked}
            disabled={disabled}
            onCheckedChange={onCheckboxCheckedChange}
            ariaLabel="Table header checkbox"
          />
        </span>
      ) : null}

      <div style={getTextContainerStyle(direction)}>
        <span
          style={{
            ...toTypographyStyle(typography.scale.captionL.regular),
            color: textBase.staticDarkSecondary,
            whiteSpace: 'nowrap',
          }}
        >
          {title}
        </span>

        {showSortIcon ? (
          <button
            type="button"
            disabled={disabled || !onSortClick}
            onClick={!disabled ? onSortClick : undefined}
            style={{
              width: spacing.scale['16'],
              height: spacing.scale['16'],
              display: 'inline-flex',
              alignItems: 'center',
              justifyContent: 'center',
              padding: spacing.scale['0'],
              borderStyle: 'solid',
              borderWidth: border.width['0'],
              borderRadius: spacing.scale['0'],
              backgroundColor: palette.base.transparent,
              color: textBase.staticDarkSecondary,
              cursor: disabled || !onSortClick ? 'default' : 'pointer',
            }}
            aria-label="Sort column"
          >
            <SortIcon size={spacing.scale['16']} color={textBase.staticDarkSecondary} />
          </button>
        ) : null}
      </div>
    </div>
  );
}

API Reference

Props 문서 준비 중입니다. (component-spec.json 추가 시 표시됩니다.)