hs-uix 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -31
- package/calendar.d.ts +1 -0
- package/common-components.d.ts +143 -0
- package/datatable.d.ts +1 -27
- package/dist/calendar.js +2003 -0
- package/dist/calendar.mjs +2004 -0
- package/dist/common-components.js +1072 -717
- package/dist/common-components.mjs +1344 -994
- package/dist/datatable.js +1114 -251
- package/dist/datatable.mjs +1089 -219
- package/dist/feed.js +1166 -154
- package/dist/feed.mjs +1170 -153
- package/dist/form.js +792 -166
- package/dist/form.mjs +693 -68
- package/dist/index.js +8354 -7077
- package/dist/index.mjs +8425 -7128
- package/dist/kanban.js +1076 -329
- package/dist/kanban.mjs +1061 -311
- package/dist/utils.js +1492 -646
- package/dist/utils.mjs +1410 -573
- package/feed.d.ts +1 -1
- package/form.d.ts +1 -28
- package/index.d.ts +54 -103
- package/kanban.d.ts +1 -1
- package/package.json +10 -6
- package/src/calendar/README.md +301 -0
- package/src/calendar/index.d.ts +201 -0
- package/src/common-components/README.md +400 -0
- package/{packages → src}/datatable/README.md +10 -10
- package/{packages → src}/datatable/index.d.ts +11 -0
- package/{packages → src}/feed/README.md +3 -1
- package/{packages → src}/feed/index.d.ts +18 -0
- package/{packages → src}/form/README.md +24 -10
- package/{packages → src}/kanban/README.md +13 -13
- package/{packages → src}/kanban/index.d.ts +18 -7
- package/src/utils/README.md +511 -0
- package/utils.d.ts +55 -3
- /package/{packages → src}/form/index.d.ts +0 -0
package/feed.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./src/feed/index";
|
package/form.d.ts
CHANGED
|
@@ -1,28 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
FormBuilder,
|
|
3
|
-
useFormPrefill,
|
|
4
|
-
FormBuilderProps,
|
|
5
|
-
FormBuilderField,
|
|
6
|
-
FormBuilderFieldType,
|
|
7
|
-
FormBuilderOption,
|
|
8
|
-
FormBuilderDateValue,
|
|
9
|
-
FormBuilderTimeValue,
|
|
10
|
-
FormBuilderDateTimeValue,
|
|
11
|
-
FormBuilderValidationContext,
|
|
12
|
-
FormBuilderValidatorResult,
|
|
13
|
-
FormBuilderValidator,
|
|
14
|
-
FormBuilderDependsOnConfig,
|
|
15
|
-
FormBuilderRepeaterProps,
|
|
16
|
-
FormBuilderGroupOptions,
|
|
17
|
-
FormBuilderLabels,
|
|
18
|
-
FormBuilderAlertConfig,
|
|
19
|
-
FormBuilderButtonsRenderContext,
|
|
20
|
-
FormBuilderSubmitAlign,
|
|
21
|
-
FormBuilderLayout,
|
|
22
|
-
FormBuilderLayoutEntry,
|
|
23
|
-
FormBuilderSection,
|
|
24
|
-
FormBuilderSectionContext,
|
|
25
|
-
FormBuilderStep,
|
|
26
|
-
FormBuilderRef,
|
|
27
|
-
FieldTypePlugin,
|
|
28
|
-
} from "./packages/form/index";
|
|
1
|
+
export * from "./src/form/index";
|
package/index.d.ts
CHANGED
|
@@ -1,107 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
DataTableParams,
|
|
11
|
-
DataTableOption,
|
|
12
|
-
DataTableDateValue,
|
|
13
|
-
DataTableTimeValue,
|
|
14
|
-
DataTableDateRangeValue,
|
|
15
|
-
DataTableWidth,
|
|
16
|
-
DataTableColumnWidth,
|
|
17
|
-
DataTableEditMode,
|
|
18
|
-
DataTableEditType,
|
|
19
|
-
DataTableSelectionAction,
|
|
20
|
-
DataTableRowAction,
|
|
21
|
-
DataTableSelectAllRequestPayload,
|
|
22
|
-
DataTableLabels,
|
|
23
|
-
DataTableSelectionBarRenderContext,
|
|
24
|
-
DataTableEmptyStateRenderContext,
|
|
25
|
-
DataTableLoadingStateRenderContext,
|
|
26
|
-
DataTableErrorStateRenderContext,
|
|
27
|
-
} from "./packages/datatable/index";
|
|
28
|
-
|
|
29
|
-
export { Kanban, KanbanCardActions } from "./packages/kanban/index";
|
|
30
|
-
export type {
|
|
31
|
-
KanbanProps,
|
|
32
|
-
KanbanStage,
|
|
33
|
-
KanbanStageMeta,
|
|
34
|
-
KanbanStageVariant,
|
|
35
|
-
KanbanStageControlMode,
|
|
36
|
-
KanbanCardField,
|
|
37
|
-
KanbanCardFieldPlacement,
|
|
38
|
-
KanbanCardDensity,
|
|
39
|
-
KanbanCardBodyMode,
|
|
40
|
-
KanbanCardDividers,
|
|
41
|
-
KanbanCardRenderContext,
|
|
42
|
-
KanbanFilterConfig,
|
|
43
|
-
KanbanFilterType,
|
|
44
|
-
KanbanSortOption,
|
|
45
|
-
KanbanSelectionAction,
|
|
46
|
-
KanbanCardAction,
|
|
47
|
-
KanbanCardActionsProps,
|
|
48
|
-
KanbanLabels,
|
|
49
|
-
KanbanParams,
|
|
50
|
-
KanbanStageChangeResult,
|
|
51
|
-
KanbanMetricItem,
|
|
52
|
-
KanbanSelectionBarRenderContext,
|
|
53
|
-
KanbanEmptyStateRenderContext,
|
|
54
|
-
KanbanLoadingStateRenderContext,
|
|
55
|
-
KanbanErrorStateRenderContext,
|
|
56
|
-
KanbanTransitionPromptContext,
|
|
57
|
-
KanbanOption,
|
|
58
|
-
} from "./packages/kanban/index";
|
|
59
|
-
|
|
60
|
-
export { Feed } from "./packages/feed/index";
|
|
61
|
-
export type {
|
|
62
|
-
FeedAction,
|
|
63
|
-
FeedActor,
|
|
64
|
-
FeedContainer,
|
|
65
|
-
FeedEmptyStateRenderContext,
|
|
66
|
-
FeedErrorStateRenderContext,
|
|
67
|
-
FeedField,
|
|
68
|
-
FeedFieldType,
|
|
69
|
-
FeedItem,
|
|
70
|
-
FeedLabels,
|
|
71
|
-
FeedLoadingStateRenderContext,
|
|
72
|
-
FeedPlacement,
|
|
73
|
-
FeedProps,
|
|
74
|
-
} from "./packages/feed/index";
|
|
1
|
+
// Each component module prefixes its type names (DataTable*, Kanban*, Feed*,
|
|
2
|
+
// Calendar*, FormBuilder*) and its value exports are unique, so `export *` is
|
|
3
|
+
// collision-free and keeps this root barrel automatically in sync with every
|
|
4
|
+
// type each component declares — no hand-maintained list to fall out of date.
|
|
5
|
+
export * from "./src/datatable/index";
|
|
6
|
+
export * from "./src/kanban/index";
|
|
7
|
+
export * from "./src/feed/index";
|
|
8
|
+
export * from "./src/calendar/index";
|
|
9
|
+
export * from "./src/form/index";
|
|
75
10
|
|
|
76
|
-
export {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
FormBuilderSubmitAlign,
|
|
95
|
-
FormBuilderLayout,
|
|
96
|
-
FormBuilderLayoutEntry,
|
|
97
|
-
FormBuilderSection,
|
|
98
|
-
FormBuilderSectionContext,
|
|
99
|
-
FormBuilderStep,
|
|
100
|
-
FormBuilderRef,
|
|
101
|
-
FieldTypePlugin,
|
|
102
|
-
} from "./packages/form/index";
|
|
103
|
-
|
|
104
|
-
export { AutoTag, AutoStatusTag, KeyValueList, SectionHeader, Spinner, SPINNERS, SPINNER_NAMES, gridToBraille, makeGrid } from "./common-components";
|
|
11
|
+
export {
|
|
12
|
+
ActiveFilterChips,
|
|
13
|
+
AutoTag,
|
|
14
|
+
AutoStatusTag,
|
|
15
|
+
CollectionCount,
|
|
16
|
+
CollectionFilterControl,
|
|
17
|
+
CollectionSortSelect,
|
|
18
|
+
CollectionToolbar,
|
|
19
|
+
CrmLookupSelect,
|
|
20
|
+
formatCollectionCount,
|
|
21
|
+
KeyValueList,
|
|
22
|
+
SectionHeader,
|
|
23
|
+
Spinner,
|
|
24
|
+
SPINNERS,
|
|
25
|
+
SPINNER_NAMES,
|
|
26
|
+
gridToBraille,
|
|
27
|
+
makeGrid,
|
|
28
|
+
} from "./common-components";
|
|
105
29
|
export {
|
|
106
30
|
Icon,
|
|
107
31
|
ICON_NAMES,
|
|
@@ -131,8 +55,11 @@ export {
|
|
|
131
55
|
DEFAULT_SVG_FONT_WEIGHT,
|
|
132
56
|
} from "./common-components";
|
|
133
57
|
export {
|
|
58
|
+
buildActiveFilterChips,
|
|
59
|
+
buildCrmSearchConfig,
|
|
134
60
|
buildOptions,
|
|
135
61
|
createStatusTagSortComparator,
|
|
62
|
+
crmSearchResultToOption,
|
|
136
63
|
CrmDataTable,
|
|
137
64
|
CrmKanban,
|
|
138
65
|
findOptionLabel,
|
|
@@ -143,8 +70,21 @@ export {
|
|
|
143
70
|
formatPercentage,
|
|
144
71
|
getAutoTagVariant,
|
|
145
72
|
getAutoStatusTagVariant,
|
|
73
|
+
getEmptyFilterValue,
|
|
74
|
+
getEmptyFilterValues,
|
|
75
|
+
isFilterActive,
|
|
76
|
+
makeCrmSearchMultiSelectField,
|
|
77
|
+
makeCrmSearchSelectField,
|
|
78
|
+
normalizeCrmSearchRecord,
|
|
79
|
+
normalizeCrmSearchRows,
|
|
80
|
+
resetFilterValues,
|
|
146
81
|
resolveCrmObjectType,
|
|
82
|
+
searchRows,
|
|
83
|
+
filterRows,
|
|
147
84
|
sumBy,
|
|
85
|
+
toStableKey,
|
|
86
|
+
useCrmSearchDataSource,
|
|
87
|
+
useCrmSearchOptions,
|
|
148
88
|
} from "./utils";
|
|
149
89
|
export type {
|
|
150
90
|
AutoTagOptions,
|
|
@@ -156,9 +96,16 @@ export type {
|
|
|
156
96
|
CrmKanbanProps,
|
|
157
97
|
CrmSearchDataSource,
|
|
158
98
|
CrmSearchConfigOptions,
|
|
99
|
+
CrmSearchOptionsDataSource,
|
|
100
|
+
CrmSearchPagination,
|
|
159
101
|
CrmSearchParams,
|
|
102
|
+
QueryFilterConfig,
|
|
103
|
+
ActiveFilterChip,
|
|
104
|
+
FilterResetOptions,
|
|
105
|
+
BuildActiveFilterChipsOptions,
|
|
160
106
|
} from "./utils";
|
|
161
107
|
export type {
|
|
108
|
+
ActiveFilterChipsProps,
|
|
162
109
|
AutoTagProps,
|
|
163
110
|
AutoStatusTagProps,
|
|
164
111
|
AvatarStackDataUriResult,
|
|
@@ -172,6 +119,10 @@ export type {
|
|
|
172
119
|
IconSize,
|
|
173
120
|
IconDataUriResult,
|
|
174
121
|
IconDataUriOptions,
|
|
122
|
+
CollectionCountProps,
|
|
123
|
+
CollectionFilterControlProps,
|
|
124
|
+
CollectionSortSelectProps,
|
|
125
|
+
CollectionToolbarProps,
|
|
175
126
|
CrmLookupSelectProps,
|
|
176
127
|
DateDirectionLabels,
|
|
177
128
|
DatePresetOption,
|
package/kanban.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./src/kanban/index";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hs-uix",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Production-ready UI components for HubSpot UI Extensions — DataTable, FormBuilder, and more",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -32,6 +32,11 @@
|
|
|
32
32
|
"import": "./dist/feed.mjs",
|
|
33
33
|
"require": "./dist/feed.js"
|
|
34
34
|
},
|
|
35
|
+
"./calendar": {
|
|
36
|
+
"types": "./calendar.d.ts",
|
|
37
|
+
"import": "./dist/calendar.mjs",
|
|
38
|
+
"require": "./dist/calendar.js"
|
|
39
|
+
},
|
|
35
40
|
"./common-components": {
|
|
36
41
|
"types": "./common-components.d.ts",
|
|
37
42
|
"import": "./dist/common-components.mjs",
|
|
@@ -50,18 +55,17 @@
|
|
|
50
55
|
"form.d.ts",
|
|
51
56
|
"kanban.d.ts",
|
|
52
57
|
"feed.d.ts",
|
|
58
|
+
"calendar.d.ts",
|
|
53
59
|
"common-components.d.ts",
|
|
54
60
|
"utils.d.ts",
|
|
55
|
-
"
|
|
56
|
-
"packages/*/README.md",
|
|
61
|
+
"src/**/*.d.ts",
|
|
57
62
|
"README.md"
|
|
58
63
|
],
|
|
59
|
-
"workspaces": [
|
|
60
|
-
"packages/*"
|
|
61
|
-
],
|
|
62
64
|
"scripts": {
|
|
63
65
|
"build": "tsup",
|
|
64
66
|
"build:icons": "node scripts/build-icons.mjs",
|
|
67
|
+
"test": "vitest run",
|
|
68
|
+
"test:watch": "vitest",
|
|
65
69
|
"dev": "tsup --watch",
|
|
66
70
|
"link:demos": "npm link && cd ../hs-uix-demos/src/app/pages && npm link hs-uix",
|
|
67
71
|
"pack:demos": "node scripts/pack-demos.mjs",
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Calendar (hs-uix/calendar)
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/hs-uix)
|
|
4
|
+
[](https://www.npmjs.com/package/hs-uix)
|
|
5
|
+
[](https://github.com/05bmckay/hs-uix/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
A presentational calendar surface for HubSpot UI Extensions. Hand it an array of records plus an `eventFields` map and it renders a **Month**, **Week**, **Day**, or **Agenda** view with a Today / ‹ › / view-switcher toolbar, optional search + filters, and click-to-open event overlays (Popover, Modal, or Panel). Like Kanban and Feed, the calendar is data-driven and presentational — the caller owns fetching.
|
|
8
|
+
|
|
9
|
+
```jsx
|
|
10
|
+
import { Calendar } from "hs-uix/calendar";
|
|
11
|
+
|
|
12
|
+
<Calendar
|
|
13
|
+
events={deals}
|
|
14
|
+
eventFields={{ id: "id", start: "closeDate", title: "name", subtitle: "owner", color: "color" }}
|
|
15
|
+
defaultView="month"
|
|
16
|
+
/>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Why Calendar?
|
|
20
|
+
|
|
21
|
+
HubSpot UI Extensions give you primitives, not a calendar. Rolling your own means building a month matrix by hand, equal-width day columns out of a `Table` with numeric widths, an hour grid that can't use `rowspan`, "+N more" overflow popovers, a Today/prev/next toolbar, and date coercion for the half-dozen shapes a CRM property can arrive in — all while working around the platform's missing height/scroll primitives. Calendar does that for you and exposes a small, controlled-or-uncontrolled API.
|
|
22
|
+
|
|
23
|
+
It maps **your** records to calendar roles via `eventFields` (a key or an accessor per role), so you don't reshape your data:
|
|
24
|
+
|
|
25
|
+
```jsx
|
|
26
|
+
const fields = {
|
|
27
|
+
id: "id",
|
|
28
|
+
start: "closeDate", // a key…
|
|
29
|
+
end: (deal) => deal.dueDate, // …or an accessor
|
|
30
|
+
title: "name",
|
|
31
|
+
subtitle: "owner",
|
|
32
|
+
color: "stageColor", // a Tag variant: default|info|success|warning|error
|
|
33
|
+
href: (deal) => `/deal/${deal.id}`,
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- Four stable views behind a `Select` switcher: **Month** (7-column day grid), **Week** / **Day** (hour-row time grid), and **Agenda** (day-grouped list)
|
|
40
|
+
- Controlled or uncontrolled `view` and `focusedDate`, with `onViewChange` / `onNavigate`
|
|
41
|
+
- Date coercion for every shape a HubSpot field arrives in: `Date`, epoch ms (number **or** string), ISO string, or a `DateInput` value object (`{ year, month, date }`, 0-indexed month) — date-only strings are parsed as **local** midnight so events never land a day early
|
|
42
|
+
- Multi-day events render across each day they touch; timed events show their start time
|
|
43
|
+
- Month-cell overflow collapses to a **"+N more"** popover; cap per cell with `maxEventsPerDay`
|
|
44
|
+
- Time grid honors `dayStartHour` / `dayEndHour`, an all-day band, and multi-hour blocks that note "↑ cont. through …"
|
|
45
|
+
- Search + filters reuse the same config shape as DataTable / Kanban (`select`, `multiselect`, `dateRange`), with optional fuzzy matching
|
|
46
|
+
- Event overlays via the standard `overlay` trigger: `overlayMode` of `"popover"` (default, experimental), `"modal"`, `"panel"`, or `"none"`, plus a `renderEventDetail` escape hatch and an always-fires `onEventClick`
|
|
47
|
+
- **Timezone support (opt-in)** — off by default (events render exactly as sent); set `timeZone` or enable the built-in `showTimeZoneSelect` dropdown and every time, grid placement, and day-grouping resolves in the chosen IANA zone, DST-correct
|
|
48
|
+
- Server-side friendly: `onRangeChange` fires on mount and whenever the visible range changes, with `loading` / `error` states
|
|
49
|
+
- Render-override slots: `renderToolbar`, `renderDayCell`, `renderEmptyState`, `renderLoadingState`, `renderErrorState`
|
|
50
|
+
- Full i18n surface via `labels`
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install hs-uix
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Import it in your card:
|
|
59
|
+
|
|
60
|
+
```jsx
|
|
61
|
+
import { Calendar } from "hs-uix/calendar";
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Requires `@hubspot/ui-extensions` >= 0.14.0 and `react` >= 18.0.0 as peer dependencies (already present in any HubSpot UI Extensions project). TypeScript declarations are bundled with `hs-uix` (`calendar.d.ts`).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Views
|
|
69
|
+
|
|
70
|
+
| View | What it shows | Navigation step |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `month` | A 7-column day grid for the focused month (5–6 week rows). Each day stacks up to `maxEventsPerDay` event chips, then a "+N more" popover. | ± 1 month |
|
|
73
|
+
| `week` | An hour-row time grid for the focused week, plus an all-day band. | ± 1 week |
|
|
74
|
+
| `day` | The single-day hour schedule (the same grid as week, one wide column) with an all-day band and a "now" current-hour marker. | ± 1 day |
|
|
75
|
+
| `agenda` | The focused week's events grouped under day headers (time · title · owner rows). | ± 1 week |
|
|
76
|
+
|
|
77
|
+
The view switcher is a `Select` (not `Tabs`) because the platform caches `Tabs` bodies and they won't re-render on data changes.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
82
|
+
|
|
83
|
+
### Deal close-date calendar
|
|
84
|
+
|
|
85
|
+
Deals plotted on their close date, colored by stage, with search + a stage filter and click-to-open popovers.
|
|
86
|
+
|
|
87
|
+
```jsx
|
|
88
|
+
const STAGE_COLOR = {
|
|
89
|
+
"Closed won": "success",
|
|
90
|
+
"Contract sent": "info",
|
|
91
|
+
Negotiation: "warning",
|
|
92
|
+
Discovery: "default",
|
|
93
|
+
"Closed lost": "error",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
<Calendar
|
|
97
|
+
events={deals.map((d) => ({ ...d, color: STAGE_COLOR[d.stage] }))}
|
|
98
|
+
eventFields={{ id: "id", start: "closeDate", title: "name", subtitle: "owner", color: "color" }}
|
|
99
|
+
defaultView="month"
|
|
100
|
+
showSearch
|
|
101
|
+
searchFields={["name", "owner", "stage"]}
|
|
102
|
+
filters={[
|
|
103
|
+
{
|
|
104
|
+
name: "stage",
|
|
105
|
+
type: "multiselect",
|
|
106
|
+
placeholder: "All stages",
|
|
107
|
+
chipLabel: "Stage",
|
|
108
|
+
options: Object.keys(STAGE_COLOR).map((s) => ({ label: s, value: s })),
|
|
109
|
+
},
|
|
110
|
+
]}
|
|
111
|
+
maxEventsPerDay={3}
|
|
112
|
+
overlayMode="popover"
|
|
113
|
+
/>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Week schedule (time grid)
|
|
117
|
+
|
|
118
|
+
A rep's week in an hour-row grid, Monday start, 8 AM–6 PM, opening a Panel per meeting.
|
|
119
|
+
|
|
120
|
+
```jsx
|
|
121
|
+
<Calendar
|
|
122
|
+
events={meetings}
|
|
123
|
+
eventFields={{ id: "id", start: "start", end: "end", title: "title", subtitle: "owner", color: "color" }}
|
|
124
|
+
defaultView="week"
|
|
125
|
+
views={["week", "day", "agenda"]}
|
|
126
|
+
weekStartsOn={1}
|
|
127
|
+
dayStartHour={8}
|
|
128
|
+
dayEndHour={18}
|
|
129
|
+
overlayMode="panel"
|
|
130
|
+
/>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Server-driven data
|
|
134
|
+
|
|
135
|
+
Fetch only the visible range. `onRangeChange` fires on mount and on every navigation / view change.
|
|
136
|
+
|
|
137
|
+
```jsx
|
|
138
|
+
const [events, setEvents] = useState([]);
|
|
139
|
+
const [loading, setLoading] = useState(true);
|
|
140
|
+
|
|
141
|
+
<Calendar
|
|
142
|
+
events={events}
|
|
143
|
+
eventFields={fields}
|
|
144
|
+
serverSide
|
|
145
|
+
loading={loading}
|
|
146
|
+
onRangeChange={async ({ start, end, view }) => {
|
|
147
|
+
setLoading(true);
|
|
148
|
+
setEvents(await fetchEvents({ start, end }));
|
|
149
|
+
setLoading(false);
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Controlled view + date
|
|
155
|
+
|
|
156
|
+
```jsx
|
|
157
|
+
const [view, setView] = useState("month");
|
|
158
|
+
const [date, setDate] = useState(new Date());
|
|
159
|
+
|
|
160
|
+
<Calendar
|
|
161
|
+
events={events}
|
|
162
|
+
eventFields={fields}
|
|
163
|
+
view={view}
|
|
164
|
+
onViewChange={setView}
|
|
165
|
+
focusedDate={date}
|
|
166
|
+
onNavigate={(next) => setDate(next)}
|
|
167
|
+
/>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Custom overlay body
|
|
171
|
+
|
|
172
|
+
Override what opens when an event is clicked (works for popover / modal / panel).
|
|
173
|
+
|
|
174
|
+
```jsx
|
|
175
|
+
<Calendar
|
|
176
|
+
events={events}
|
|
177
|
+
eventFields={fields}
|
|
178
|
+
overlayMode="modal"
|
|
179
|
+
renderEventDetail={(event) => (
|
|
180
|
+
<Flex direction="column" gap="sm">
|
|
181
|
+
<Text format={{ fontWeight: "demibold" }}>{event.title}</Text>
|
|
182
|
+
<Link href={`/deal/${event.id}`}>Open deal</Link>
|
|
183
|
+
</Flex>
|
|
184
|
+
)}
|
|
185
|
+
onEventClick={(raw, event) => track("event_opened", { id: event.id })}
|
|
186
|
+
/>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Timezones
|
|
190
|
+
|
|
191
|
+
**Timezones are off by default** — the calendar renders each event exactly as the data provides it (no conversion), so you never have to think about zones unless you want to. To pin a specific zone — or let users switch — opt into the timezone layer. Every time, hour-grid placement, and day-grouping then resolves in the chosen IANA zone, DST-correct.
|
|
192
|
+
|
|
193
|
+
```jsx
|
|
194
|
+
// Built-in selector in the toolbar ("UTC −05:00 Central Time"); the layer starts at UTC:
|
|
195
|
+
<Calendar events={events} eventFields={fields} defaultView="week" showTimeZoneSelect />
|
|
196
|
+
|
|
197
|
+
// Or pin a zone with no selector:
|
|
198
|
+
<Calendar events={events} eventFields={fields} timeZone="America/New_York" />
|
|
199
|
+
|
|
200
|
+
// Controlled, with a custom zone list:
|
|
201
|
+
<Calendar
|
|
202
|
+
events={events}
|
|
203
|
+
eventFields={fields}
|
|
204
|
+
showTimeZoneSelect
|
|
205
|
+
timeZone={tz}
|
|
206
|
+
onTimeZoneChange={setTz}
|
|
207
|
+
timeZoneOptions={["UTC", "America/Chicago", "Europe/London", "Asia/Tokyo"]}
|
|
208
|
+
/>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
How it works: each event instant is converted to a "wall-clock" `Date` in the active zone, so the existing local-time grid logic resolves in that zone with no other changes. Leave all the `timeZone*` props unset (and `showTimeZoneSelect` off) and the calendar does no conversion at all — events render as-sent in the viewer's local time.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## API Reference
|
|
216
|
+
|
|
217
|
+
### Calendar Props
|
|
218
|
+
|
|
219
|
+
| Prop | Type | Default | Description |
|
|
220
|
+
|---|---|---|---|
|
|
221
|
+
| `events` | `Event[]` | — | Your records. The calendar is presentational; you own fetching. |
|
|
222
|
+
| `eventFields` | `CalendarEventFields` | sensible keys | Maps event fields to roles (`start` / `end` / `title` / `subtitle` / `color` / `href` / `id`). Each is a key or an accessor. |
|
|
223
|
+
| `view` | `CalendarView` | — | Controlled current view. |
|
|
224
|
+
| `defaultView` | `CalendarView` | `"month"` | Uncontrolled initial view. |
|
|
225
|
+
| `onViewChange` | `(view) => void` | — | Fires when the view changes. |
|
|
226
|
+
| `views` | `CalendarView[]` | all | Which views to expose in the switcher (month / week / day / agenda). |
|
|
227
|
+
| `focusedDate` | `CalendarDateInput` | — | Controlled focused date. |
|
|
228
|
+
| `defaultFocusedDate` | `CalendarDateInput` | today | Uncontrolled initial date. |
|
|
229
|
+
| `onNavigate` | `(date, { view }) => void` | — | Fires on prev / next / today. |
|
|
230
|
+
| `weekStartsOn` | `0 \| 1` | `0` | 0 = Sunday, 1 = Monday. |
|
|
231
|
+
| `hideWeekends` | `boolean` | `false` | Hide Sat/Sun in month + week views. |
|
|
232
|
+
| `maxEventsPerDay` | `number` | `3` | Chips per month cell before "+N more". |
|
|
233
|
+
| `dayStartHour` | `number` | `8` | First hour row in week/day (0–23). |
|
|
234
|
+
| `dayEndHour` | `number` | `20` | Last hour row in week/day (0–23). |
|
|
235
|
+
| `timeZone` | `string` | — | Controlled IANA zone (e.g. `"America/Chicago"`). Setting it opts into the tz layer; all times/placement/grouping resolve here. |
|
|
236
|
+
| `defaultTimeZone` | `string` | — | Uncontrolled initial zone. Opts into the tz layer; the layer itself starts at `"UTC"` if engaged without this. |
|
|
237
|
+
| `onTimeZoneChange` | `(tz) => void` | — | Fires when the zone changes. |
|
|
238
|
+
| `showTimeZoneSelect` | `boolean` | `false` | Show the built-in zone dropdown. Opts into the tz layer (starts at UTC). Off ⇒ no tz layer, events render as-sent. |
|
|
239
|
+
| `timeZoneOptions` | `(string \| { value, label? })[]` | curated list | Override the selector's zones. |
|
|
240
|
+
| `filters` | `CalendarFilterConfig[]` | — | `select` / `multiselect` / `dateRange` filters (DataTable shape). |
|
|
241
|
+
| `searchFields` | `string[]` | — | Top-level keys searched by the search box. **Required** for `showSearch` to match anything. |
|
|
242
|
+
| `searchValue` / `onSearchChange` | `string` / `fn` | — | Controlled search. |
|
|
243
|
+
| `filterValues` / `onFilterChange` | `object` / `fn` | — | Controlled filter values. |
|
|
244
|
+
| `fuzzySearch` | `boolean` | `false` | Fuzzy match (Fuse.js) instead of substring. |
|
|
245
|
+
| `showSearch` | `boolean` | `false` | Show the search box (pair with `searchFields`). |
|
|
246
|
+
| `serverSide` | `boolean` | `false` | Treat data as server-provided. |
|
|
247
|
+
| `loading` | `boolean` | `false` | Render the loading state. |
|
|
248
|
+
| `error` | `string \| boolean` | `false` | Render the error state. |
|
|
249
|
+
| `onRangeChange` | `({ start, end, view }) => void` | — | Fires on mount + range change — fetch here. |
|
|
250
|
+
| `overlayMode` | `"popover" \| "modal" \| "panel" \| "none"` | `"popover"` | How an event opens. Popover is experimental. |
|
|
251
|
+
| `renderEventDetail` | `(event) => ReactNode` | — | Override the overlay body. |
|
|
252
|
+
| `onEventClick` | `(raw, event) => void` | — | Always fires on event click. |
|
|
253
|
+
| `renderToolbar` | `(api) => ReactNode` | — | Replace the toolbar. |
|
|
254
|
+
| `renderDayCell` | `(day, events) => ReactNode` | — | Replace a month day cell's body. |
|
|
255
|
+
| `renderLoadingState` / `renderErrorState` | `fn` | — | Replace those states. |
|
|
256
|
+
| `renderEmptyState` | `fn` | — | Replace the empty state in the **agenda** view (the month/week/day grids always render). |
|
|
257
|
+
| `labels` | `CalendarLabels` | English | i18n strings. |
|
|
258
|
+
|
|
259
|
+
### `eventFields`
|
|
260
|
+
|
|
261
|
+
| Role | Type | Notes |
|
|
262
|
+
|---|---|---|
|
|
263
|
+
| `id` | key \| `(event) => string \| number` | Stable id (used for React keys + overlay ids). |
|
|
264
|
+
| `start` | key \| accessor → `CalendarDateInput` | Required for an event to appear. |
|
|
265
|
+
| `end` | key \| accessor → `CalendarDateInput` | Optional; enables multi-day + duration. |
|
|
266
|
+
| `title` | key \| accessor → `ReactNode` | The event label. |
|
|
267
|
+
| `subtitle` | key \| accessor → `ReactNode` | Secondary text (e.g. owner). |
|
|
268
|
+
| `color` | key \| accessor → `string` | A Tag variant: `default` / `info` / `success` / `warning` / `error`. |
|
|
269
|
+
| `href` | `CalendarHref` \| accessor | `string` or `{ url, external? }`. |
|
|
270
|
+
|
|
271
|
+
`CalendarDateInput` = `Date` | epoch `number` | ISO/epoch `string` | `{ year, month, date, hour?, minute? }` (0-indexed `month`).
|
|
272
|
+
|
|
273
|
+
### Normalized event
|
|
274
|
+
|
|
275
|
+
Render callbacks (`renderEventDetail`, `onEventClick`, `renderDayCell`) receive the normalized event:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
{ key, id, start: Date | null, end: Date | null, title, subtitle, color, href, raw }
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`raw` is your original record.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Limitations
|
|
286
|
+
|
|
287
|
+
These come from HubSpot UI Extensions itself, not Calendar:
|
|
288
|
+
|
|
289
|
+
| Limitation | Details |
|
|
290
|
+
|---|---|
|
|
291
|
+
| No height / vertical scroll | Layout primitives have no `height` / `overflow`. Long lists expand the page; the month grid caps chips per cell (`maxEventsPerDay`) and overflows to a popover. |
|
|
292
|
+
| View switching is a `Select` | `Tabs` cache their body and don't re-render on data changes, so the switcher is a `Select`. |
|
|
293
|
+
| Time grid can't span rows | No `rowspan` / height control, so a multi-hour block repeats with a "↑ cont. through …" note rather than one tall block. |
|
|
294
|
+
| Equal day columns via numeric widths | Equal-width columns require a `Table` with equal **numeric** widths (content-sized `flex` columns come out uneven). |
|
|
295
|
+
| Popover is experimental | `overlayMode="popover"` uses `@hubspot/ui-extensions/experimental`. Use `"modal"` / `"panel"` if you need a stable overlay. |
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## License
|
|
300
|
+
|
|
301
|
+
MIT
|