{"id":48305687,"url":"https://github.com/05bmckay/hs-uix","last_synced_at":"2026-05-29T20:00:25.831Z","repository":{"id":348543997,"uuid":"1196701698","full_name":"05bmckay/hs-uix","owner":"05bmckay","description":"Production-ready UI components for HubSpot UI Extensions — DataTable, FormBuilder, and more","archived":false,"fork":false,"pushed_at":"2026-05-28T22:40:58.000Z","size":13146,"stargazers_count":22,"open_issues_count":3,"forks_count":3,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-05-29T00:19:51.249Z","etag":null,"topics":["crm","datatable","formbuilder","hubspot","hubspot-crm","react","ui-extensions"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/org/hs-uix","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/05bmckay.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-31T00:42:04.000Z","updated_at":"2026-05-28T22:41:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"41df0c91-7e58-4cb3-863c-d16b407b9980","html_url":"https://github.com/05bmckay/hs-uix","commit_stats":null,"previous_names":["05bmckay/hubspot-datatable","05bmckay/hs-uix"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/05bmckay/hs-uix","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/05bmckay%2Fhs-uix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/05bmckay%2Fhs-uix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/05bmckay%2Fhs-uix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/05bmckay%2Fhs-uix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/05bmckay","download_url":"https://codeload.github.com/05bmckay/hs-uix/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/05bmckay%2Fhs-uix/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33668186,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crm","datatable","formbuilder","hubspot","hubspot-crm","react","ui-extensions"],"created_at":"2026-04-05T00:00:39.144Z","updated_at":"2026-05-29T20:00:25.821Z","avatar_url":"https://github.com/05bmckay.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hs-uix\n\n[![npm version](https://img.shields.io/npm/v/hs-uix)](https://www.npmjs.com/package/hs-uix)\n[![license](https://img.shields.io/npm/l/hs-uix)](./LICENSE)\n\nProduction-ready UI components for [HubSpot UI Extensions](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensions/overview). Built entirely on HubSpot's native primitives — no custom HTML, no CSS, no iframes.\n\n## Install\n\n```bash\nnpm install hs-uix\n```\n\n```jsx\nimport { DataTable } from \"hs-uix/datatable\";\nimport { FormBuilder } from \"hs-uix/form\";\nimport { Feed } from \"hs-uix/feed\";\nimport { Icon, AutoStatusTag, AutoTag, CrmLookupSelect, KeyValueList, SectionHeader } from \"hs-uix/common-components\";\nimport { CrmDataTable, CrmKanban, formatCurrency, formatDate } from \"hs-uix/utils\";\n\n// or import everything from the root\nimport { DataTable, FormBuilder, Icon, AutoStatusTag, AutoTag } from \"hs-uix\";\n```\n\nRequires `react` \u003e= 18.0.0 and `@hubspot/ui-extensions` \u003e= 0.12.0 as peer dependencies (already present in any HubSpot UI Extensions project).\n\n## Components\n\n| Component | Description | Docs |\n|-----------|-------------|------|\n| **DataTable** | Filterable, sortable, paginated table with auto-sized columns, inline editing, row grouping, and more | [Full documentation](https://github.com/05bmckay/hs-uix/blob/main/packages/datatable/README.md) |\n| **FormBuilder** | Declarative, config-driven form with validation, multi-step wizards, and 20+ field types | [Full documentation](https://github.com/05bmckay/hs-uix/blob/main/packages/form/README.md) |\n| **Kanban** | Stage-based board with filters, sort, headline metrics, card action bars, and DataTable-parity card field config | [Full documentation](https://github.com/05bmckay/hs-uix/blob/main/packages/kanban/README.md) |\n| **Feed** | Activity feed / timeline with a standard item shape, date grouping, load-more pagination, and HubSpot-native item regions | [Full documentation](https://github.com/05bmckay/hs-uix/blob/main/packages/feed/README.md) |\n| **Common Components** | Thin visual wrappers over HubSpot primitives — `Icon`, `AutoTag`, `AutoStatusTag`, `AvatarStack`, `CrmLookupSelect`, `SectionHeader`, `KeyValueList`, `StyledText` | [Full documentation](https://github.com/05bmckay/hs-uix/blob/main/src/common-components/README.md) |\n| **CRM data** | `CrmDataTable` / `CrmKanban` — batch-fetching, client-side-paginating CRM table \u0026 board, plus the `useCrmSearch*` hooks behind them | [Full documentation](https://github.com/05bmckay/hs-uix/blob/main/src/utils/README.md) |\n| **Utils** | Pure helpers for formatting, options, HubSpot value guards, and tag-variant inference | [Full documentation](https://github.com/05bmckay/hs-uix/blob/main/src/utils/README.md) |\n\n---\n\n# DataTable\n\nA drop-in table component for HubSpot UI Extensions. Define your columns, pass your data, and you get search, filtering, sorting, pagination, inline editing, row grouping, and auto-sized columns out of the box.\n\n![Full-Featured DataTable](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/datatable/assets/fully-featured-table.png)\n\n## Quick Start\n\n```jsx\nimport { DataTable } from \"hs-uix/datatable\";\nimport { AutoStatusTag, AutoTag, KeyValueList, SectionHeader } from \"hs-uix/common-components\";\nimport { formatCurrency, formatDate } from \"hs-uix/utils\";\n\nconst COLUMNS = [\n  { field: \"name\", label: \"Company\", sortable: true, renderCell: (val) =\u003e val },\n  { field: \"status\", label: \"Status\", renderCell: (val) =\u003e \u003cAutoStatusTag value={val} /\u003e },\n  { field: \"segment\", label: \"Segment\", renderCell: (val) =\u003e \u003cAutoTag value={val} /\u003e },\n  { field: \"amount\", label: \"Amount\", sortable: true, renderCell: (val) =\u003e formatCurrency(val) },\n  { field: \"closeDate\", label: \"Close Date\", renderCell: (val) =\u003e formatDate(val) },\n];\n\n\u003cDataTable data={deals} columns={COLUMNS} searchFields={[\"name\"]} pageSize={10} /\u003e\n\n\u003cSectionHeader\n  title=\"Deal Summary\"\n  description=\"A compact summary block using common components.\"\n/\u003e\n\n\u003cKeyValueList\n  items={[\n    { label: \"Open deals\", value: 12 },\n    { label: \"Pipeline\", value: formatCurrency(245000) },\n  ]}\n/\u003e\n```\n\nThat's a searchable, sortable, paginated table with auto-sized columns in 5 lines of config.\n\n## Features\n\n- Full-text search with optional fuzzy matching via Fuse.js\n- Select, multi-select, and date range filters with active badges and clear/reset controls\n- Click-to-sort headers with three-state cycling\n- Client-side or server-side pagination\n- Collapsible row groups with per-column aggregation\n- Row selection with bulk action bar\n- Per-row actions via `rowActions`\n- Two edit modes (discrete click-to-edit and inline always-visible) supporting 12 input types with validation\n- Auto-width column sizing based on data analysis\n- Column-level footer for totals rows\n- Works with `useAssociations` for live CRM data\n- Server-side mode with loading/error states, search debounce, and unified `onParamsChange` callback\n\n## Highlights\n\n### Filters \u0026 Footer Totals\n\n![Active Filters](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/datatable/assets/fully-featured-table-active-filters.png)\n\n### Row Selection \u0026 Bulk Actions\n\n![Row Selection with Action Bar](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/datatable/assets/action-bar-per-row-actions.png)\n\n### Inline Editing\n\n![Discrete Editing](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/datatable/assets/inline-editing-discreet.png)\n\nTwo edit modes: **discrete** (click-to-edit, default) and **inline** (always-visible inputs). Supports `text`, `textarea`, `number`, `currency`, `stepper`, `select`, `multiselect`, `date`, `time`, `datetime`, `toggle`, and `checkbox`.\n\n### Row Grouping\n\n![Row Grouping](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/datatable/assets/row-grouping.png)\n\n### Full-Row Editing\n\n![Full-Row Editing](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/datatable/assets/full-row-editing.png)\n\n### useAssociations\n\n![useAssociations + DataTable](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/datatable/assets/useAssociations.png)\n\nConnect live CRM data (contacts, deals, tickets, etc.) to a DataTable with `useAssociations` from `@hubspot/ui-extensions/crm`.\n\n---\n\n# FormBuilder\n\nDeclarative, config-driven forms for HubSpot UI Extensions. Define fields as data, get a complete form with validation, layout, multi-step wizards, and full HubSpot component integration.\n\n![Basic Form](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/form/assets/basic-form.png)\n\n## Quick Start\n\n```jsx\nimport { FormBuilder } from \"hs-uix/form\";\n\nconst fields = [\n  { name: \"firstName\", type: \"text\", label: \"First name\", required: true },\n  { name: \"lastName\", type: \"text\", label: \"Last name\", required: true },\n  { name: \"email\", type: \"text\", label: \"Email\", pattern: /^[^\\s@]+@[^\\s@]+$/, patternMessage: \"Enter a valid email\" },\n];\n\n\u003cFormBuilder\n  columns={2}\n  fields={fields}\n  onSubmit={(values) =\u003e console.log(values)}\n/\u003e\n```\n\n## Features\n\n- 20+ field types mapping to native HubSpot components (`text`, `number`, `select`, `date`, `toggle`, `repeater`, `crmPropertyList`, and more)\n- Four layout modes: fixed columns, responsive AutoGrid, explicit row layout, and legacy half-width\n- Built-in validation chain: required, pattern, length/range, custom sync, and custom async with loading indicators\n- Conditional visibility and dependent property grouping\n- Multi-step wizards with per-step validation\n- Repeater fields for dynamic add/remove rows\n- Accordion sections and field group dividers\n- Custom field type plugin registry\n- Controlled and uncontrolled modes\n- Ref API for imperative access (`submit`, `validate`, `reset`, `getValues`, `setFieldValue`, etc.)\n- Submit lifecycle hooks (`transformValues`, `onBeforeSubmit`, `onSubmitSuccess`, `onSubmitError`)\n- Auto-save, dirty tracking, read-only mode, and form-level alerts\n\n## Highlights\n\n### Layout\n\n![Explicit Layout](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/form/assets/explicit-layout-weighted.png)\n\n### Conditional Visibility \u0026 Dependent Properties\n\n![Dependent \u0026 Cascading](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/form/assets/dependent-cascading.gif)\n\n### Async Validation\n\n![Async Validation](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/form/assets/async-validation-side-effects.png)\n\n### Repeater Fields\n\n![Repeater Fields](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/form/assets/repeater-fields.png)\n\n### Sections \u0026 Groups\n\n![Sections \u0026 Groups](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/form/assets/section-and-groups.png)\n\n### Custom Field Types\n\n![Custom Field Types](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/form/assets/custom-field-types.png)\n\n### Display Options\n\n![Display Options](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/form/assets/display-options.png)\n\n### Read-Only Mode\n\n![Read-Only Mode](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/form/assets/readonly-autosave-dirty.png)\n\n---\n\n# Kanban\n\nA stage-based board view that shares DataTable's config vocabulary (`cardFields` ≈ `columns`, filters, sort, selection) so you can offer users a table-or-board toggle without rewriting the data layer. Drag-and-drop isn't available inside HubSpot UI Extensions, so stage changes happen through an inline `Select` (or menu) on each card.\n\n![Kanban — HubSpot Deals preset with metrics](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/kanban/assets/hubspot-deals-preset-with-metrics.png)\n\n## Quick Start\n\n```jsx\nimport { Kanban } from \"hs-uix/kanban\";\nimport { AutoTag } from \"hs-uix/common-components\";\nimport { formatCurrencyCompact, formatDate } from \"hs-uix/utils\";\n\nconst STAGES = [\n  { value: \"qualified\",     label: \"Qualified\",      variant: \"info\" },\n  { value: \"proposal\",      label: \"Proposal\",       variant: \"info\" },\n  { value: \"negotiation\",   label: \"Negotiation\",    variant: \"warning\" },\n  { value: \"closed_won\",    label: \"Closed Won\",     variant: \"success\", terminal: true },\n  { value: \"closed_lost\",   label: \"Closed Lost\",    variant: \"default\", terminal: true },\n];\n\nconst CARD_FIELDS = [\n  { field: \"name\",      placement: \"title\" },\n  { field: \"company\",   placement: \"subtitle\" },\n  { field: \"amount\",    placement: \"meta\",   render: (val) =\u003e formatCurrencyCompact(val) },\n  { field: \"segment\",   placement: \"body\",   render: (val) =\u003e \u003cAutoTag value={val} /\u003e },\n  { field: \"closeDate\", placement: \"footer\", render: (val) =\u003e formatDate(val) },\n];\n\n\u003cKanban\n  data={deals}\n  stages={STAGES}\n  groupBy=\"stage\"\n  cardFields={CARD_FIELDS}\n  onStageChange={(row, newStage) =\u003e updateDealStage(row.id, newStage)}\n/\u003e\n```\n\n## Features\n\n- **Stage-based columns** with variant-colored headers (`success` / `warning` / `info` / `default`), collapse-to-rail, and stage-level count badges\n- **`cardFields` with placement** (`title` / `subtitle` / `meta` / `body` / `footer`) — same render/truncate/visible hooks as DataTable columns\n- **Filters and sort** with the same config shape as DataTable (`select`, `multiselect`, `dateRange`)\n- **Headline metrics** rendered above the board (deal totals, weighted pipeline, win rate) via a `metrics` prop\n- **Stage transition prompts** — async confirmation or extra-property capture before committing a stage change, declared per-stage via `stage.onEnterRequired.render`\n- **Selection bar + card actions** for bulk moves, deletes, or custom handlers (`KanbanCardActions`)\n- **Empty / loading / error render slots** that mirror DataTable's override API\n- **Paired view adapters** — use `deriveCardFieldsFromColumns` from `hs-uix/utils` to project a DataTable `columns` config into Kanban `cardFields` with a single function call\n\n## Highlights\n\n### HubSpot Deals preset\n\n![Kanban — HubSpot Deals preset with metrics](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/kanban/assets/hubspot-deals-preset-with-metrics.png)\n\nDrop-in preset shaped like HubSpot's native deals pipeline: stage-variant headers, per-stage amount totals in the header metric, and an avatar-stack footer row. Hide the summary row with `showMetrics={false}` for dashboards that already surface totals elsewhere.\n\n![Kanban — HubSpot Deals preset without metrics](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/kanban/assets/hubspot-deals-preset-no-metrics.png)\n\n### Compact lead board\n\n![Kanban — Compact lead board preset](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/kanban/assets/compact-lead-board-preset.png)\n\n`cardDensity=\"compact\"` with trimmed `cardFields` for high-volume boards (leads, tickets, tasks) where you want to fit 8-12 cards per column on a typical viewport without horizontal scrolling.\n\n### Load-more \u0026 stage controls\n\n![Kanban — Select + load more preset](https://raw.githubusercontent.com/05bmckay/hs-uix/main/packages/kanban/assets/select-load-more-preset.png)\n\nPer-stage pagination via an `onLoadMore` handler and `stageMeta.hasMore`, plus inline `Select` stage controls on each card (`stageControl=\"select\"`). Switch to `\"menu\"` for action-menu style transitions, or `\"none\"` for read-only boards.\n\n---\n\n# Common Components\n\nThin, composable visual wrappers built on HubSpot's native primitives — the reusable pieces that show up across DataTable rows, FormBuilder cells, and Kanban cards. Use them to skip rewriting the same status-tag / avatar-stack / label-value block on every surface.\n\n## Quick Start\n\n```jsx\nimport {\n  AutoStatusTag,\n  AutoTag,\n  AvatarStack,\n  SectionHeader,\n  KeyValueList,\n  StyledText,\n  HS_DATE_PRESETS,\n} from \"hs-uix/common-components\";\nimport { formatCurrency } from \"hs-uix/utils\";\n\n\u003cSectionHeader\n  title=\"Deal Summary\"\n  description=\"A compact summary block using common components.\"\n/\u003e\n\n\u003cKeyValueList\n  items={[\n    { label: \"Status\", value: \u003cAutoStatusTag value=\"At risk\" /\u003e },\n    { label: \"Segment\", value: \u003cAutoTag value=\"Enterprise\" /\u003e },\n    { label: \"Owners\", value: \u003cAvatarStack items={[\"AR\", \"JK\", \"SP\", \"MB\", \"LM\"]} maxVisible={4} /\u003e },\n    { label: \"Pipeline\", value: formatCurrency(245000) },\n  ]}\n/\u003e\n```\n\n## What's inside\n\n- `Icon` — a superset of HubSpot's native `\u003cIcon\u003e`: custom glyphs, any CSS color, and pixel sizes, delegating to the native component whenever the request is natively expressible\n- `AutoStatusTag` — `StatusTag` with variant inferred from the value (`Active` → success, `At risk` → warning, `Failed` → danger, etc.)\n- `AutoTag` — `Tag` with the same inference, for non-status labels\n- `AvatarStack` — overlapping circular avatars as a single SVG (letters, image URLs, or mixed); `+N` overflow chip past `maxVisible`\n- `CrmLookupSelect` — CRM-backed `Select` / `MultiSelect` with live, debounced search\n- `SectionHeader` — title + optional description + actions slot\n- `KeyValueList` — vertical list of label/value rows via `DescriptionList`\n- `StyledText` — SVG-rendered text with rotation, custom color, and pill backgrounds for cases native `\u003cText\u003e` can't express\n\nPlus low-level builders (`makeAvatarStackDataUri`, `makeStyledTextDataUri`) that return `{ src, width, height }` for composing into larger SVGs, and style constants (`HS_FONT_FAMILY`, `HS_TEXT_COLOR`, `HS_SUBTLE_BG`, `HS_MUTED_TEXT`, `HS_NEUTRAL_CHIP`) that mirror HubSpot's native CSS — so custom SVGs sit alongside the rest of the UI without a color mismatch.\n\n## Highlights\n\n### AutoTag \u0026 AutoStatusTag\n\n![AutoTag variants](https://raw.githubusercontent.com/05bmckay/hs-uix/main/src/common-components/assets/auto-tag.png)\n\n![AutoStatusTag variants](https://raw.githubusercontent.com/05bmckay/hs-uix/main/src/common-components/assets/auto-status-tag.png)\n\nPass a free-form status string and get a properly-colored tag back. Matching is case-insensitive and tolerates underscores / dashes / phrases (`\"in_progress\"`, `\"on hold\"`, `\"at-risk\"` all resolve). Override via `overrides={{ \"Processing\": \"warning\" }}` and `fallback=\"info\"` for values that don't match built-in heuristics.\n\n### AvatarStack\n\n![Avatar stack](https://raw.githubusercontent.com/05bmckay/hs-uix/main/src/common-components/assets/avatar-stack.png)\n\nOverlapping avatars rendered as a single SVG via `\u003cImage\u003e`. T-shirt sizing (`xs` → `xl`) or a raw pixel number. Letters auto-color from the built-in palette; image URLs get circular-clipped. Extras past `maxVisible` collapse into a neutral `+N` chip.\n\n### Icon\n\nA superset of HubSpot's native `\u003cIcon\u003e`. When the request is natively expressible (a whitelisted `name`, a semantic `color`, an `sm`/`md`/`lg` `size`) it **delegates to the real `\u003cIcon\u003e`** — keeping auto-sizing, `color=\"inherit\"`, and screen-reader semantics. Otherwise it renders a registered SVG glyph as a data-URI `\u003cImage\u003e`, lifting all three native limits: custom/unregistered glyphs (~248 bundled in `ICONS`), any CSS color, and `xs`–`xl` tokens or a pixel size. Add your own glyphs via `svgToIconEntry`, or build a data URI directly with `makeIconDataUri`.\n\n### CrmLookupSelect\n\n![CrmLookupSelect live search](https://raw.githubusercontent.com/05bmckay/hs-uix/main/src/common-components/assets/crmLookUp.gif)\n\nPoint it at a CRM `objectType` + `properties` and get a debounced, paginated `Select` / `MultiSelect` that searches live as the user types. Picked options stay valid after results change, `loadingOption` shows during the debounce window, and `noResultsOption` only appears once a query settles — no \"no results\" flash mid-type.\n\n### SectionHeader \u0026 KeyValueList\n\n![KeyValueList](https://raw.githubusercontent.com/05bmckay/hs-uix/main/src/common-components/assets/key-value-list.png)\n\nPair `SectionHeader` (title / description / action slot) with `KeyValueList` (`DescriptionList` rows) for compact summary panels. `direction=\"column\"` on the list switches to stacked label-on-top rows.\n\n### StyledText\n\n![Styled text](https://raw.githubusercontent.com/05bmckay/hs-uix/main/src/common-components/assets/styled-text.png)\n\nReach for `StyledText` when native `\u003cText\u003e` can't do what you need: vertical rail labels (`orientation=\"vertical-down\"`), HubSpot-style pill badges (`background={{ preset: \"tag\" }}`), or a specific glyph color. Plain horizontal `preset: \"tag\"` usage renders through native HubSpot `Tag`; rotated/custom tag cases still use the SVG fallback. Use native `\u003cText\u003e` anywhere copy-paste matters.\n\n### HS_DATE_PRESETS\n\nHubSpot's native quick-date preset list (`Today`, `Last 7 days`, `This quarter`, …) as a ready-to-use `options` array for `DataTable` / `Kanban` select filters. Values are stable identifiers (`\"today\"`, `\"7d\"`, `\"this_quarter\"`) — translate to date bounds via `filterFn` or server-side in `onFilterChange`.\n\n---\n\n# Utils\n\nPure helper functions for formatting values, building option arrays, detecting HubSpot-shaped date/time objects, and inferring tag variants from raw data. Zero side effects, no JSX — drop them into `renderCell`, `sortComparator`, or a server handler.\n\n## Quick Start\n\n```jsx\nimport {\n  formatCurrency,\n  formatCurrencyCompact,\n  formatDate,\n  formatDateTime,\n  formatPercentage,\n  buildOptions,\n  findOptionLabel,\n  getAutoTagVariant,\n  createStatusTagSortComparator,\n  sumBy,\n} from \"hs-uix/utils\";\n\nformatCurrency(1234.56);              // → \"$1,235\"\nformatCurrencyCompact(123_580_000);   // → \"$123.6M\"\nformatDate(\"2026-04-15\");             // → \"Apr 15, 2026\"\nformatPercentage(0.1567);             // → \"16%\"\n\nconst statusOptions = buildOptions(\n  [{ name: \"Open\", id: \"o\" }, { name: \"Closed\", id: \"c\" }],\n  { labelKey: \"name\", valueKey: \"id\" },\n);\nfindOptionLabel(statusOptions, \"o\"); // → \"Open\"\n\ngetAutoTagVariant(\"At risk\");         // → \"warning\"\nsumBy(deals, \"amount\");               // → total\n```\n\n## What's inside\n\n- **`formatters.js`** — locale-aware `Intl`-based number / currency / date / percentage formatters. Every formatter accepts a trailing options object that spreads into the underlying `Intl` call, so anything `Intl.NumberFormat` supports (narrow symbol, specific fraction digits, grouping) is reachable without a new helper.\n- **`options.js`** — `buildOptions(items, opts?)` to shape raw arrays into `{ label, value }` for HubSpot `Select` / `MultiSelect`; `findOptionLabel(options, value, fallback?)` for the reverse lookup.\n- **`hubspotValues.js`** — type guards for HubSpot's `DateInput` / `TimeInput` / `DateTimeInput` value shapes (`isDateValueObject`, `isTimeValueObject`, `isDateTimeValueObject`). Use in `filterFn` or `sortComparator` to distinguish a HubSpot date-object from a raw string/Date.\n- **`tagVariants.js`** — heuristic mappers from free-form status strings to semantic tag variants (`getAutoTagVariant`, `getAutoStatusTagVariant`, `getAutoTagDisplayValue`) plus `createStatusTagSortComparator` for DataTable columns grouped by color, then alphabetical within each color.\n- **`collections.js`** — `sumBy(items, keyOrFn)` for total / weighted-total rows, safe against `null` / missing values.\n\n## Highlights\n\n### Formatters\n\n```js\nformatCurrency(9500, { currency: \"EUR\" });            // → \"€9,500\"\nformatCurrencyCompact(4160);                          // → \"$4.2K\"\nformatDate(Date.now(), { month: \"numeric\" });         // → \"4/15/2026\"\nformatDateTime(\"2026-04-15T14:30:00Z\");               // → \"Apr 15, 2026, 9:30 AM\" (local)\nformatPercentage(0.1567, { maximumFractionDigits: 1 });// → \"15.6%\"\n```\n\nEvery formatter treats `null` / `undefined` as safe — `formatCurrency(null)` → `\"$0\"`, `formatDate(null)` → `\"\"` — so they're safe to drop into cells rendering partially-loaded data.\n\n### Tag Variants\n\nThe same inference that powers `AutoTag` / `AutoStatusTag`, exposed as plain functions for use in custom cells and sort comparators. Default ordering: `success → warning → danger/error → info → default`; override via `variantOrder` on `createStatusTagSortComparator`.\n\n### Options \u0026 HubSpot Value Guards\n\nBuild select options from CRM records, resolve labels back to values, and detect HubSpot's structured date/time value objects in one import — no more ad-hoc `.map(r =\u003e ({ label: r.name, value: r.id }))` at every call site.\n\n---\n\n## Migrating from `@hs-uix/datatable` or `@hs-uix/form`\n\nBoth packages have been merged into `hs-uix`. Update your imports:\n\n```diff\n- import { DataTable } from \"@hs-uix/datatable\";\n+ import { DataTable } from \"hs-uix/datatable\";\n\n- import { FormBuilder } from \"@hs-uix/form\";\n+ import { FormBuilder } from \"hs-uix/form\";\n```\n\n```bash\nnpm uninstall @hs-uix/datatable @hs-uix/form\nnpm install hs-uix\n```\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F05bmckay%2Fhs-uix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F05bmckay%2Fhs-uix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F05bmckay%2Fhs-uix/lists"}