Properties

Property Schema

Define your data schema with property types that power filtering, sorting, grouping, and display across all views.

A property schema defines how each field is displayed, filtered, sorted, grouped, and searched.

Full Example

This example shows all 13 property types in a real-world product schema:

product-properties.tsx
import { FilesMediaProperty } from "@sparkyidea/dataview/properties";
import type { DataViewProperty } from "@sparkyidea/dataview/types";
import type { AppRouter } from "@sparkyidea/trpc/routers/index";
import type { inferRouterOutputs } from "@trpc/server";
import { AlertCircle, CheckCircle2, Edit, Eye, XCircle } from "lucide-react";
import { toast } from "sonner";

type RouterOutput = inferRouterOutputs<AppRouter>;
type ProductWithVariants = RouterOutput["product"]["getMany"]["items"][number];

export type Product = ProductWithVariants;

export const productProperties = [
  {
    key: "productName",
    name: "Text",
    type: "text",
  },
  {
    key: "price",
    name: "Number",
    type: "number",
    config: {
      numberFormat: "dollar",
      decimalPlaces: 2,
    },
  },
  {
    key: "stockLevel",
    name: "Number (Bar)",
    type: "number",
    config: {
      showAs: {
        type: "bar",
        color: "red",
        divideBy: 100,
      },
    },
  },
  {
    key: "rating",
    name: "Number (Ring)",
    type: "number",
    config: {
      showAs: {
        type: "ring",
        color: "green",
        divideBy: 100,
      },
    },
  },
  {
    key: "category",
    name: "Select",
    type: "select",
    config: {
      options: [
        { value: "Accessories", color: "blue" },
        { value: "Bottoms", color: "purple" },
        { value: "Dresses", color: "pink" },
        { value: "Footwear", color: "yellow" },
        { value: "Garden", color: "green" },
        { value: "Home", color: "teal" },
        { value: "Jewelry", color: "gray" },
        { value: "Lingerie", color: "red" },
        { value: "Outerwear", color: "gray" },
        { value: "Tops", color: "blue" },
      ],
    },
  },
  {
    key: "tags",
    name: "Multi Select",
    type: "multiSelect",
    config: {
      options: [
        { value: "Bestseller", color: "yellow" },
        { value: "Eco-friendly", color: "green" },
        { value: "Limited Edition", color: "purple" },
        { value: "New Arrival", color: "blue" },
        { value: "On Sale", color: "red" },
        { value: "Seasonal", color: "pink" },
      ],
    },
  },
  {
    key: "availability",
    name: "Status",
    type: "status",
    config: {
      groups: [
        {
          name: "Available",
          color: "green",
          options: ["In stock"],
          icon: CheckCircle2,
        },
        {
          name: "Warning",
          color: "yellow",
          options: ["Low stock"],
          icon: AlertCircle,
        },
        {
          name: "Unavailable",
          color: "red",
          options: ["Out of stock"],
          icon: XCircle,
        },
      ],
    },
  },
  {
    key: "lastRestocked",
    name: "Date",
    type: "date",
  },
  {
    key: "featured",
    name: "Checkbox",
    type: "checkbox",
  },
  {
    key: "productImage",
    name: "Files & Media",
    type: "filesMedia",
  },
  {
    key: "productLink",
    name: "URL",
    type: "url",
    enableGroup: false,
  },
  {
    key: "supplierPhone",
    name: "Phone",
    type: "phone",
  },
  {
    key: "supplierEmail",
    name: "Email",
    type: "email",
  },
  {
    id: "_totalWorth",
    name: "Formula",
    type: "formula",
    value: (property, item) => (
      <div className="flex items-center gap-4">
        <FilesMediaProperty className="h-10 w-10" value={item.productImage} />
        <div className="flex flex-col items-start justify-center gap-2">
          {property("productName")}
          <div className="flex items-center gap-2">
            {property("price")}
            {property("category")}
            {property("availability")}
          </div>
        </div>
      </div>
    ),
  },
  {
    id: "actions",
    name: "Actions",
    type: "button",
    value: (item) => [
      {
        label: "View",
        icon: Eye,
        onClick: () => toast.info(`Viewing: ${item.productName}`),
      },
      {
        label: "Edit",
        icon: Edit,
        onClick: () => toast.info(`Editing: ${item.productName}`),
      },
    ],
  },
] as DataViewProperty<Product>[];

Property Types

TypeDescriptionConfig Required
textPlain text❌ No
numberNumeric with formattingOptional
selectSingle selection badgeOptional
multiSelectMultiple selection badgesOptional
statusGrouped status with iconsOptional
dateDate/time valuesOptional
checkboxBoolean❌ No
urlClickable linksOptional
emailEmail addresses❌ No
phonePhone numbers❌ No
filesMediaImages and files❌ No
formulaComputed JSXvalue() required
buttonRow action buttonsvalue() required

Base Property Fields

{
  id: string;             // Property identifier
  type: PropertyType;     // One of 13 types
  label?: string;         // Display label
  hidden?: boolean;       // Hide in view columns/cards
  enableFilter?: boolean; // In filter picker (default: true)
  enableSort?: boolean;   // In sort picker (default: true)
  enableGroup?: boolean;  // In group picker (default: true)
  enableSearch?: boolean; // In search (default: type-dependent)
}

Number Config

{
  id: "price",
  type: "number",
  config: {
    numberFormat: "dollar", // number | numberWithCommas | percentage | dollar | euro | pound
    decimalPlaces: 2,
    showAs: {
      type: "bar",          // number | bar | ring
      color: "green",
      divideBy: 100,
      showNumber: true,
    },
  },
}

Select / MultiSelect Config

{
  id: "category",
  type: "select", // or "multiSelect"
  config: {
    optionOrder: "manual", // manual | asc | desc
    options: [
      { value: "Electronics", color: "blue" },
      { value: "Clothing", color: "purple" },
    ],
  },
}

Status Config

{
  id: "availability",
  type: "status",
  config: {
    groups: [
      {
        label: "Available",
        color: "green",
        icon: CheckCircle,
        options: ["In stock", "Ready"],
      },
      {
        label: "Warning",
        color: "yellow",
        icon: AlertCircle,
        options: ["Low stock"],
      },
    ],
  },
}

Date Config

{
  id: "createdAt",
  type: "date",
  config: {
    dateFormat: "relative", // full | short | MDY | DMY | YMD | relative | relativeGroup
    timeFormat: "12hour",   // hidden | 12hour | 24hour
  },
}

Formula Property

Use formula to compose JSX from existing properties.

{
  id: "productCard",
  type: "formula",
  sortBy: "productName",
  value: (property, item) => (
    <div className="flex items-center gap-4">
      <FilesMediaProperty value={item.productImage} />
      <div className="flex flex-col gap-1">
        {property("productName")}
        <div className="flex gap-2">
          {property("price")}
          {property("category")}
        </div>
      </div>
    </div>
  ),
}

Button Property

{
  id: "actions",
  type: "button",
  value: (item) => [
    {
      label: "View",
      icon: Eye,
      onClick: () => openModal(item.id),
    },
    {
      label: "Edit",
      icon: Edit,
      onClick: () => router.push(`/edit/${item.id}`),
      disabled: !item.canEdit,
    },
  ],
}

Type Safety Notes

import type { DataViewProperty } from "@sparkyidea/dataview/types";

interface Product {
  id: string;
  productName: string;
  price: number;
}

const properties = [
  { id: "productName", type: "text" },
  { id: "price", type: "number", config: { numberFormat: "dollar" } },
] satisfies DataViewProperty<Product>[];

DataViewProperty<T> validates property shape and type-specific config. Property id is currently a runtime string (not keyof T), so unknown IDs are not compile-time errors.