hs-uix 2.1.0 → 2.2.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 +3 -1
- package/common-components.d.ts +319 -68
- package/dist/calendar.js +397 -119
- package/dist/calendar.mjs +399 -119
- package/dist/common-components.js +3546 -88
- package/dist/common-components.mjs +3530 -84
- package/dist/datatable.js +108 -18
- package/dist/datatable.mjs +108 -18
- package/dist/experimental.js +2876 -0
- package/dist/experimental.mjs +2883 -0
- package/dist/feed.js +267 -38
- package/dist/feed.mjs +260 -37
- package/dist/filter.js +1379 -0
- package/dist/filter.mjs +1334 -0
- package/dist/form.js +222 -26
- package/dist/form.mjs +227 -27
- package/dist/index.js +3255 -353
- package/dist/index.mjs +3199 -344
- package/dist/kanban.js +282 -62
- package/dist/kanban.mjs +273 -61
- package/dist/safe.js +9207 -0
- package/dist/safe.mjs +9298 -0
- package/dist/utils.js +491 -75
- package/dist/utils.mjs +491 -75
- package/experimental.d.ts +1 -0
- package/filter.d.ts +1 -0
- package/index.d.ts +45 -3
- package/package.json +19 -1
- package/safe.d.ts +1 -0
- package/src/calendar/README.md +76 -5
- package/src/calendar/index.d.ts +108 -1
- package/src/common-components/README.md +140 -1
- package/src/datatable/README.md +0 -2
- package/src/experimental/README.md +126 -0
- package/src/experimental/index.d.ts +346 -0
- package/src/feed/README.md +69 -0
- package/src/feed/index.d.ts +103 -0
- package/src/filter/README.md +148 -0
- package/src/filter/index.d.ts +221 -0
- package/src/form/README.md +132 -4
- package/src/form/index.d.ts +82 -1
- package/src/kanban/README.md +119 -6
- package/src/kanban/index.d.ts +153 -2
- package/src/safe/README.md +108 -0
- package/src/safe/index.d.ts +158 -0
- package/src/utils/README.md +39 -0
- package/src/wizard/README.md +158 -0
- package/src/wizard/index.d.ts +138 -0
- package/utils.d.ts +17 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# experimental (`hs-uix/experimental`)
|
|
2
|
+
|
|
3
|
+
APIs that are shipping for real-world feedback but whose surface may still
|
|
4
|
+
change in a minor release. Graduating components move to their stable subpath
|
|
5
|
+
with a re-export left behind for one minor version.
|
|
6
|
+
|
|
7
|
+
Currently here:
|
|
8
|
+
|
|
9
|
+
- **`Wizard` / `OnboardingChecklist`** — re-exported from `hs-uix/wizard`.
|
|
10
|
+
- **`ExperimentalDataTable`** — DataTable with the in-progress row-expansion API.
|
|
11
|
+
- **`Skeleton`** (+ `SkeletonText`, `SkeletonBox`, `SkeletonCircle`,
|
|
12
|
+
`SkeletonTable`, `makeSkeletonDataUri`, `SKELETON_WIDTH_TOKENS`) — loading
|
|
13
|
+
placeholders, documented below.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Skeleton
|
|
18
|
+
|
|
19
|
+
Content placeholders for loading states. `Spinner` says "something is
|
|
20
|
+
happening"; `Skeleton` holds the **shape** of the incoming content so the
|
|
21
|
+
layout doesn't jump when data lands. There is no native skeleton component and
|
|
22
|
+
CSS is forbidden, so each placeholder is a gray rounded-rect SVG data URI
|
|
23
|
+
rendered through the native `<Image>` — the same technique as `StyledText` and
|
|
24
|
+
`AvatarStack`.
|
|
25
|
+
|
|
26
|
+
One component, two modes.
|
|
27
|
+
|
|
28
|
+
### Wrapper mode (auto) — the default way to use it
|
|
29
|
+
|
|
30
|
+
Wrap any surface, pass `loading`, done. While loading, `Skeleton` never
|
|
31
|
+
renders its children — it reads each child element's component name and props
|
|
32
|
+
and draws a placeholder with the same footprint; when `loading` flips false
|
|
33
|
+
the children render untouched.
|
|
34
|
+
|
|
35
|
+
```jsx
|
|
36
|
+
import { Skeleton } from "hs-uix/experimental";
|
|
37
|
+
|
|
38
|
+
<Skeleton loading={isLoading}>
|
|
39
|
+
<DataTable data={rows} columns={COLUMNS} pageSize={10} />
|
|
40
|
+
</Skeleton>
|
|
41
|
+
// while loading → a 10-row table skeleton with COLUMNS.length columns
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Recognized children and what sizes their placeholder:
|
|
45
|
+
|
|
46
|
+
| Child | Shape | Sized from |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `DataTable` / `CrmDataTable` | table rows | `columns.length` / `properties.length`, `pageSize` |
|
|
49
|
+
| native `Table` | table rows | 4 × 3 default |
|
|
50
|
+
| `Kanban` / `CrmKanban` | board columns of cards | `stages.length` |
|
|
51
|
+
| `Feed` | avatar + text rows | `pageSize` |
|
|
52
|
+
| `FormBuilder` / native `Form` | label + input rows | `fields.length` |
|
|
53
|
+
| `KeyValueList` / `DescriptionList` | label/value pairs | `items.length` / child count |
|
|
54
|
+
| native `Statistics` | metric blocks | child count |
|
|
55
|
+
| native field inputs (`Select`, `Input`, `DateInput`, …) | one label + input row | — |
|
|
56
|
+
| `Button` / `Tag` / `StatusTag` | small pill | — |
|
|
57
|
+
| `BarChart` / `LineChart` / `Calendar` / `Tile` / `Card` / `Image` / … | block | chart/image height |
|
|
58
|
+
| `Text` / `Heading` / `List` | text lines | child count |
|
|
59
|
+
| anything else | text block | `lines` (default 3) |
|
|
60
|
+
|
|
61
|
+
Native components work because they are remote *string* types (`"Table"`,
|
|
62
|
+
`"Select"`) — the element type is the name. hs-uix components carry explicit
|
|
63
|
+
`displayName`s, so matching survives minified bundles.
|
|
64
|
+
|
|
65
|
+
Overrides, when the inference isn't what you want:
|
|
66
|
+
|
|
67
|
+
```jsx
|
|
68
|
+
// Pick the shape yourself…
|
|
69
|
+
<Skeleton loading={isLoading} variant="board" columns={4}>…</Skeleton>
|
|
70
|
+
|
|
71
|
+
// …or supply your own static blocks
|
|
72
|
+
<Skeleton
|
|
73
|
+
loading={isLoading}
|
|
74
|
+
skeleton={
|
|
75
|
+
<Flex direction="column" gap="md">
|
|
76
|
+
<SkeletonBox height={48} />
|
|
77
|
+
<SkeletonText lines={4} />
|
|
78
|
+
</Flex>
|
|
79
|
+
}
|
|
80
|
+
>
|
|
81
|
+
<MyCustomPanel … />
|
|
82
|
+
</Skeleton>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`variant` replaces the inferred shape (`"table" | "board" | "list" | "form" |
|
|
86
|
+
"keyvalue" | "stats" | "input" | "chip" | "block" | "text" | "box" |
|
|
87
|
+
"circle"`); `rows` / `columns` / `lines` / `height` refine it. Multiple
|
|
88
|
+
children each get their own skeleton, stacked in a column.
|
|
89
|
+
|
|
90
|
+
### Static mode — the building blocks
|
|
91
|
+
|
|
92
|
+
Without children, `Skeleton` is the placeholder primitive itself, and the
|
|
93
|
+
composite shapes work standalone too:
|
|
94
|
+
|
|
95
|
+
```jsx
|
|
96
|
+
import {
|
|
97
|
+
Skeleton,
|
|
98
|
+
SkeletonText,
|
|
99
|
+
SkeletonBox,
|
|
100
|
+
SkeletonCircle,
|
|
101
|
+
SkeletonTable,
|
|
102
|
+
} from "hs-uix/experimental";
|
|
103
|
+
|
|
104
|
+
{loading ? <SkeletonText lines={3} width="md" /> : <Text>{description}</Text>}
|
|
105
|
+
|
|
106
|
+
<Flex direction="row" gap="sm" align="center">
|
|
107
|
+
<SkeletonCircle size={32} />
|
|
108
|
+
<SkeletonText lines={2} width="sm" height={10} gap={6} />
|
|
109
|
+
</Flex>
|
|
110
|
+
|
|
111
|
+
<SkeletonTable rows={5} columns={4} />
|
|
112
|
+
<Skeleton variant="board" columns={3} /> {/* composite variants work statically */}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Prop | Type | Default | Notes |
|
|
116
|
+
| ---- | ---- | ------- | ----- |
|
|
117
|
+
| `loading` | boolean | `false` | Wrapper mode: gate for `children`. |
|
|
118
|
+
| `skeleton` | node | — | Wrapper mode: your own placeholder; skips inference. |
|
|
119
|
+
| `variant` | shape | `"text"` | Static shape, or inference override in wrapper mode. |
|
|
120
|
+
| `width` | px \| `"sm"`\|`"md"`\|`"lg"` | varies | Tokens = 120 / 240 / 360 (`SKELETON_WIDTH_TOKENS`). |
|
|
121
|
+
| `height` | px | varies | Line height (text), block height (box), diameter (circle). |
|
|
122
|
+
| `lines` / `rows` / `columns` | number | varies | Sizing for text / composite shapes. |
|
|
123
|
+
| `lastLineWidth`, `gap`, `radius`, `columnGap`, `fill`, `alt` | — | — | Primitive styling (see JSDoc). |
|
|
124
|
+
|
|
125
|
+
`makeSkeletonDataUri(opts)` returns `{ src, width, height }` for composing
|
|
126
|
+
placeholders into larger SVGs.
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { DataTableProps } from "../datatable/index";
|
|
3
|
+
import type {
|
|
4
|
+
ActiveFilterChipsProps,
|
|
5
|
+
AutoStatusTagProps,
|
|
6
|
+
AutoTagProps,
|
|
7
|
+
AvatarStackProps,
|
|
8
|
+
CollectionCountProps,
|
|
9
|
+
CollectionFilterControlProps,
|
|
10
|
+
CollectionSortSelectProps,
|
|
11
|
+
CollectionToolbarProps,
|
|
12
|
+
CrmLookupSelectProps,
|
|
13
|
+
CrmRecordPickerCreateOptionRules,
|
|
14
|
+
CrmRecordPickerId,
|
|
15
|
+
CrmRecordPickerOption,
|
|
16
|
+
CrmRecordPickerOptionConfig,
|
|
17
|
+
CrmRecordPickerProps,
|
|
18
|
+
CrmRecordPickerRecord,
|
|
19
|
+
CrmRecordPickerSelection,
|
|
20
|
+
CrmRecordPickerValue,
|
|
21
|
+
FormatCollectionCountParams,
|
|
22
|
+
KeyValueListProps,
|
|
23
|
+
SectionHeaderProps,
|
|
24
|
+
StyledTextFormat,
|
|
25
|
+
StyledTextProps,
|
|
26
|
+
} from "../../common-components";
|
|
27
|
+
|
|
28
|
+
export * from "../wizard/index";
|
|
29
|
+
|
|
30
|
+
export interface ExperimentalDataTableRowExpansionProps<
|
|
31
|
+
Row = Record<string, unknown>,
|
|
32
|
+
Id = string | number
|
|
33
|
+
> {
|
|
34
|
+
/** Enables expandable detail rows. Content renders in a full-span row directly under the data row. */
|
|
35
|
+
renderExpandedRow?: (row: Row) => ReactNode;
|
|
36
|
+
/** Controlled expansion state — array of expanded row IDs. */
|
|
37
|
+
expandedRowIds?: Id[];
|
|
38
|
+
/** Uncontrolled initial expansion state. */
|
|
39
|
+
defaultExpandedRowIds?: Id[];
|
|
40
|
+
/** Called with the next expanded id array on every toggle. */
|
|
41
|
+
onExpandedRowsChange?: (expandedRowIds: Id[]) => void;
|
|
42
|
+
/** "icon" adds a chevron column; "row" toggles through cell content links. */
|
|
43
|
+
expandOn?: "icon" | "row";
|
|
44
|
+
/** Accordion mode — expanding a row collapses the others. */
|
|
45
|
+
expandSingle?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type ExperimentalDataTableProps<
|
|
49
|
+
Row = Record<string, unknown>,
|
|
50
|
+
Id = string | number
|
|
51
|
+
> = DataTableProps<Row, Id> & ExperimentalDataTableRowExpansionProps<Row, Id>;
|
|
52
|
+
|
|
53
|
+
export declare function DataTable<
|
|
54
|
+
Row = Record<string, unknown>,
|
|
55
|
+
Id = string | number
|
|
56
|
+
>(props: ExperimentalDataTableProps<Row, Id>): ReactElement | null;
|
|
57
|
+
|
|
58
|
+
export { DataTable as ExperimentalDataTable };
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Skeleton loading placeholders (experimental)
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
export type SkeletonShape =
|
|
65
|
+
| "table"
|
|
66
|
+
| "board"
|
|
67
|
+
| "list"
|
|
68
|
+
| "form"
|
|
69
|
+
| "keyvalue"
|
|
70
|
+
| "stats"
|
|
71
|
+
| "input"
|
|
72
|
+
| "chip"
|
|
73
|
+
| "block";
|
|
74
|
+
|
|
75
|
+
export type SkeletonVariant = "text" | "box" | "circle" | SkeletonShape;
|
|
76
|
+
|
|
77
|
+
/** Pixel number or a width token: sm = 120, md = 240, lg = 360. */
|
|
78
|
+
export type SkeletonWidth = number | "sm" | "md" | "lg";
|
|
79
|
+
|
|
80
|
+
export interface SkeletonDataUriOptions {
|
|
81
|
+
variant?: "text" | "box" | "circle";
|
|
82
|
+
width?: SkeletonWidth;
|
|
83
|
+
/** Per-line height for "text" (default 12), block height for "box" (default 96), diameter for "circle" (default 40). */
|
|
84
|
+
height?: number;
|
|
85
|
+
/** "text" only: number of stacked lines. Default 1. */
|
|
86
|
+
lines?: number;
|
|
87
|
+
/** "text" only: final-line width when lines > 1. (0, 1] = fraction of width; > 1 = px; tokens allowed. Default 0.6. */
|
|
88
|
+
lastLineWidth?: SkeletonWidth;
|
|
89
|
+
/** "text" only: px between lines. Default 8. */
|
|
90
|
+
gap?: number;
|
|
91
|
+
/** Corner radius px (ignored for "circle"). Default 3. */
|
|
92
|
+
radius?: number;
|
|
93
|
+
/** "box" only: split the block into N equal cells. Default 1. */
|
|
94
|
+
columns?: number;
|
|
95
|
+
/** "box" only: px between cells. Default 16. */
|
|
96
|
+
columnGap?: number;
|
|
97
|
+
/** Placeholder color. Default SKELETON_FILL. */
|
|
98
|
+
fill?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface SkeletonDataUriResult {
|
|
102
|
+
src: string;
|
|
103
|
+
width: number;
|
|
104
|
+
height: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface SkeletonProps extends Omit<SkeletonDataUriOptions, "variant"> {
|
|
108
|
+
/** Static shape, or the override for the inferred shape in wrapper mode. */
|
|
109
|
+
variant?: SkeletonVariant;
|
|
110
|
+
/** Wrapper mode: while true, children are replaced by shape-matched placeholders. Default false. */
|
|
111
|
+
loading?: boolean;
|
|
112
|
+
/** Wrapper mode: your own placeholder node(s); skips auto-inference. */
|
|
113
|
+
skeleton?: ReactNode;
|
|
114
|
+
/** Wrapper mode: content gated by `loading`. */
|
|
115
|
+
children?: ReactNode;
|
|
116
|
+
/** Composite shapes: row count. */
|
|
117
|
+
rows?: number;
|
|
118
|
+
/** Accessible label on the underlying <Image>. Default "Loading". */
|
|
119
|
+
alt?: string;
|
|
120
|
+
[imageProp: string]: unknown;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface SkeletonTextProps extends Omit<SkeletonProps, "variant"> {
|
|
124
|
+
/** Default 3. */
|
|
125
|
+
lines?: number;
|
|
126
|
+
/** Default "md" (240). */
|
|
127
|
+
width?: SkeletonWidth;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface SkeletonBoxProps extends Omit<SkeletonProps, "variant"> {
|
|
131
|
+
/** Default "md" (240). */
|
|
132
|
+
width?: SkeletonWidth;
|
|
133
|
+
/** Default 96. */
|
|
134
|
+
height?: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface SkeletonCircleProps
|
|
138
|
+
extends Omit<SkeletonProps, "variant" | "width" | "height"> {
|
|
139
|
+
/** Diameter px. Default 40. */
|
|
140
|
+
size?: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface SkeletonTableProps {
|
|
144
|
+
/** Default 4. */
|
|
145
|
+
rows?: number;
|
|
146
|
+
/** Cells per row. Default 3. */
|
|
147
|
+
columns?: number;
|
|
148
|
+
/** Total row width: px or token. Default "lg" (360). */
|
|
149
|
+
width?: SkeletonWidth;
|
|
150
|
+
/** Px height of each row's cells. Default 16. */
|
|
151
|
+
rowHeight?: number;
|
|
152
|
+
/** Px between cells within a row. Default 16. */
|
|
153
|
+
columnGap?: number;
|
|
154
|
+
/** Flex gap token between rows. Default "sm". */
|
|
155
|
+
gap?: string;
|
|
156
|
+
/** Cell corner radius px. Default 3. */
|
|
157
|
+
radius?: number;
|
|
158
|
+
fill?: string;
|
|
159
|
+
/** Accessible label applied to each row image. Default "Loading table". */
|
|
160
|
+
alt?: string;
|
|
161
|
+
[flexProp: string]: unknown;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export type SpinnerName =
|
|
165
|
+
| "braille"
|
|
166
|
+
| "braillewave"
|
|
167
|
+
| "dna"
|
|
168
|
+
| "scan"
|
|
169
|
+
| "rain"
|
|
170
|
+
| "scanline"
|
|
171
|
+
| "pulse"
|
|
172
|
+
| "snake"
|
|
173
|
+
| "sparkle"
|
|
174
|
+
| "cascade"
|
|
175
|
+
| "columns"
|
|
176
|
+
| "orbit"
|
|
177
|
+
| "breathe"
|
|
178
|
+
| "waverows"
|
|
179
|
+
| "checkerboard"
|
|
180
|
+
| "helix"
|
|
181
|
+
| "fillsweep"
|
|
182
|
+
| "diagswipe";
|
|
183
|
+
|
|
184
|
+
export interface SpinnerPreset {
|
|
185
|
+
frames: readonly string[];
|
|
186
|
+
interval: number;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface SpinnerProps {
|
|
190
|
+
name?: SpinnerName | string;
|
|
191
|
+
frames?: readonly string[];
|
|
192
|
+
interval?: number;
|
|
193
|
+
label?: ReactNode;
|
|
194
|
+
children?: ReactNode;
|
|
195
|
+
paused?: boolean;
|
|
196
|
+
gap?: string;
|
|
197
|
+
variant?: "bodytext" | "microcopy";
|
|
198
|
+
format?: StyledTextFormat;
|
|
199
|
+
inline?: boolean;
|
|
200
|
+
truncate?: boolean | { tooltipText?: string };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export type IconSize =
|
|
204
|
+
| number
|
|
205
|
+
| "xs"
|
|
206
|
+
| "extra-small"
|
|
207
|
+
| "sm"
|
|
208
|
+
| "small"
|
|
209
|
+
| "md"
|
|
210
|
+
| "med"
|
|
211
|
+
| "medium"
|
|
212
|
+
| "lg"
|
|
213
|
+
| "large"
|
|
214
|
+
| "xl"
|
|
215
|
+
| "extra-large";
|
|
216
|
+
|
|
217
|
+
export interface IconPathObject {
|
|
218
|
+
d: string;
|
|
219
|
+
fill?: string;
|
|
220
|
+
fillRule?: "nonzero" | "evenodd";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export type IconPath = string | IconPathObject;
|
|
224
|
+
|
|
225
|
+
export interface IconEntry {
|
|
226
|
+
/** Defaults to "0 0 24 24" when omitted. */
|
|
227
|
+
viewBox?: string;
|
|
228
|
+
paths: IconPath[];
|
|
229
|
+
/** Optional transform applied to all paths (e.g. a mirror/rotation). */
|
|
230
|
+
transform?: string;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface IconProps {
|
|
234
|
+
/** A registered glyph name (native or custom). Unknown names render nothing. */
|
|
235
|
+
name: string;
|
|
236
|
+
/** A semantic token ("inherit" | "alert" | "warning" | "success") or any CSS color. */
|
|
237
|
+
color?: string;
|
|
238
|
+
size?: IconSize;
|
|
239
|
+
/** Accessible label for screen readers. */
|
|
240
|
+
screenReaderText?: string;
|
|
241
|
+
/** Passed through to native HubSpot Icon when possible; fallback Image also receives it. */
|
|
242
|
+
onClick?: (...args: unknown[]) => void;
|
|
243
|
+
/** Passed through to native HubSpot Icon when possible; fallback Image also receives it. */
|
|
244
|
+
href?: string | { url: string; external?: boolean };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface IconDataUriResult {
|
|
248
|
+
src: string;
|
|
249
|
+
width: number;
|
|
250
|
+
height: number;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface IconDataUriOptions {
|
|
254
|
+
size?: IconSize;
|
|
255
|
+
color?: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export declare function Icon(props: IconProps): ReactNode;
|
|
259
|
+
/** Custom glyph names registered in this library (excludes native names). */
|
|
260
|
+
export declare const ICON_NAMES: string[];
|
|
261
|
+
/** The custom glyph registry, keyed by icon name. */
|
|
262
|
+
export declare const ICONS: Record<string, IconEntry>;
|
|
263
|
+
/** The native `@hubspot/ui-extensions` `<Icon>` name whitelist, sorted. */
|
|
264
|
+
export declare const NATIVE_ICON_NAME_LIST: string[];
|
|
265
|
+
/** Build an SVG data URI from a registered name or an inline entry. Null for unknown names. */
|
|
266
|
+
export declare function makeIconDataUri(
|
|
267
|
+
nameOrEntry: string | IconEntry,
|
|
268
|
+
options?: IconDataUriOptions
|
|
269
|
+
): IconDataUriResult | null;
|
|
270
|
+
/** Parse a raw `<svg>` string into a registry entry (drops mask/defs, keeps per-path fills). */
|
|
271
|
+
export declare function svgToIconEntry(raw: string): IconEntry;
|
|
272
|
+
|
|
273
|
+
export declare function AutoTag(props: AutoTagProps): ReactNode;
|
|
274
|
+
export declare function AutoStatusTag(props: AutoStatusTagProps): ReactNode;
|
|
275
|
+
export declare function ActiveFilterChips(props: ActiveFilterChipsProps): ReactNode;
|
|
276
|
+
export declare function CollectionCount(props: CollectionCountProps): ReactNode;
|
|
277
|
+
export declare function formatCollectionCount(params: FormatCollectionCountParams): ReactNode;
|
|
278
|
+
export declare function CollectionFilterControl(props: CollectionFilterControlProps): ReactNode;
|
|
279
|
+
export declare function CollectionSortSelect(props: CollectionSortSelectProps): ReactNode;
|
|
280
|
+
export declare function CollectionToolbar(props: CollectionToolbarProps): ReactNode;
|
|
281
|
+
export declare function SectionHeader(props: SectionHeaderProps): ReactNode;
|
|
282
|
+
export declare function KeyValueList(props: KeyValueListProps): ReactNode;
|
|
283
|
+
export declare function AvatarStack(props: AvatarStackProps): ReactNode;
|
|
284
|
+
export declare function CrmLookupSelect(props: CrmLookupSelectProps): ReactNode;
|
|
285
|
+
export declare function CrmRecordPicker(props: CrmRecordPickerProps): ReactNode;
|
|
286
|
+
|
|
287
|
+
/** Sentinel option value for CrmRecordPicker's inline "Create" option. */
|
|
288
|
+
export declare const CREATE_OPTION_VALUE: "__create__";
|
|
289
|
+
/** True when the value looks like a record object (vs a scalar id). */
|
|
290
|
+
export declare function isRecordLike(value: unknown): boolean;
|
|
291
|
+
/** Extract a record's id (objectId | id | hs_object_id | properties.hs_object_id). */
|
|
292
|
+
export declare function getRecordId(record: unknown): CrmRecordPickerId | undefined;
|
|
293
|
+
/** Normalize a picker value (ids and/or records, scalar or array) to { ids, records }. */
|
|
294
|
+
export declare function normalizeRecordSelection(
|
|
295
|
+
value: CrmRecordPickerValue | null | undefined
|
|
296
|
+
): CrmRecordPickerSelection;
|
|
297
|
+
/** Map a record to a { label, value, description? } option. */
|
|
298
|
+
export declare function recordToPickerOption(
|
|
299
|
+
record: CrmRecordPickerRecord,
|
|
300
|
+
config?: CrmRecordPickerOptionConfig
|
|
301
|
+
): CrmRecordPickerOption;
|
|
302
|
+
/** Merge search-page options with selected options so selections stay visible. */
|
|
303
|
+
export declare function mergePickerOptions(
|
|
304
|
+
options: CrmRecordPickerOption[],
|
|
305
|
+
selectedOptions?: CrmRecordPickerOption | CrmRecordPickerOption[] | null
|
|
306
|
+
): CrmRecordPickerOption[];
|
|
307
|
+
/** Trim a selection to its first `max` ids (rejects picks beyond the cap). */
|
|
308
|
+
export declare function enforceSelectionMax(
|
|
309
|
+
ids: CrmRecordPickerId[] | CrmRecordPickerId | null | undefined,
|
|
310
|
+
max?: number
|
|
311
|
+
): CrmRecordPickerId[];
|
|
312
|
+
/** Injection rules for the inline create option. */
|
|
313
|
+
export declare function shouldShowCreateOption(
|
|
314
|
+
rules?: CrmRecordPickerCreateOptionRules
|
|
315
|
+
): boolean;
|
|
316
|
+
/** Build the `Create "<term>"` option for a search term. */
|
|
317
|
+
export declare function makeCreateOption(
|
|
318
|
+
term: string,
|
|
319
|
+
label?: string | ((term: string) => string)
|
|
320
|
+
): CrmRecordPickerOption;
|
|
321
|
+
/** Split an onChange payload into real ids + whether the create sentinel was chosen. */
|
|
322
|
+
export declare function splitCreateSelection(
|
|
323
|
+
next: CrmRecordPickerId[] | CrmRecordPickerId | null | undefined
|
|
324
|
+
): { ids: CrmRecordPickerId[]; create: boolean };
|
|
325
|
+
/** Map ids back to records via a Map/object registry; unknown ids become { objectId } stubs. */
|
|
326
|
+
export declare function mapIdsToRecords(
|
|
327
|
+
ids: CrmRecordPickerId[] | CrmRecordPickerId | null | undefined,
|
|
328
|
+
recordsById?: Map<CrmRecordPickerId, CrmRecordPickerRecord> | Record<string, CrmRecordPickerRecord>
|
|
329
|
+
): CrmRecordPickerRecord[];
|
|
330
|
+
/** Upsert records into a list deduped by record id (later wins). */
|
|
331
|
+
export declare function upsertRecords(
|
|
332
|
+
records: CrmRecordPickerRecord[] | null | undefined,
|
|
333
|
+
additions: CrmRecordPickerRecord | CrmRecordPickerRecord[] | null | undefined
|
|
334
|
+
): CrmRecordPickerRecord[];
|
|
335
|
+
export declare function StyledText(props: StyledTextProps): ReactNode;
|
|
336
|
+
export declare function Skeleton(props: SkeletonProps): ReactNode;
|
|
337
|
+
export declare function SkeletonText(props: SkeletonTextProps): ReactNode;
|
|
338
|
+
export declare function SkeletonBox(props: SkeletonBoxProps): ReactNode;
|
|
339
|
+
export declare function SkeletonCircle(props: SkeletonCircleProps): ReactNode;
|
|
340
|
+
export declare function SkeletonTable(props: SkeletonTableProps): ReactNode;
|
|
341
|
+
/** Build the SVG data URI + intrinsic dimensions for a skeleton placeholder. */
|
|
342
|
+
export declare function makeSkeletonDataUri(
|
|
343
|
+
options?: SkeletonDataUriOptions
|
|
344
|
+
): SkeletonDataUriResult;
|
|
345
|
+
/** Width tokens accepted anywhere a skeleton takes a `width` (sm/md/lg → px). */
|
|
346
|
+
export declare const SKELETON_WIDTH_TOKENS: { sm: number; md: number; lg: number };
|
package/src/feed/README.md
CHANGED
|
@@ -75,6 +75,8 @@ If the primary mental model is “what happened, and when?”, use Feed. If the
|
|
|
75
75
|
- Built-in item count (`5 of 12 events`) with custom `recordLabel` / `itemCountText`
|
|
76
76
|
- View-more pagination (`pageSize`) plus server/external load-more (`hasMore`, `onLoadMore`)
|
|
77
77
|
- Client-side or server-side mode (`serverSide`) with unified `onParamsChange`
|
|
78
|
+
- Real-time append: `newItemsBehavior="pill"` buffers live arrivals behind a "Show N new items" pill; `highlightNew` marks fresh items with an info `Tag`
|
|
79
|
+
- Per-type display presets (`typePresets` + `DEFAULT_FEED_TYPE_PRESETS`) covering HubSpot's standard activity types with verified native icon names
|
|
78
80
|
- Built-in loading, error, and empty states with render overrides
|
|
79
81
|
- Tile-backed outer/item containers and divider mode
|
|
80
82
|
- Render escape hatches for item, toolbar, and individual item regions
|
|
@@ -183,6 +185,64 @@ Feed shows `pageSize` items initially and a transparent “View more” button w
|
|
|
183
185
|
/>
|
|
184
186
|
```
|
|
185
187
|
|
|
188
|
+
## Real-time append
|
|
189
|
+
|
|
190
|
+
Live feeds prepend items while the user is reading. By default (`newItemsBehavior="immediate"`) new items just render. Set `newItemsBehavior="pill"` to hold items that arrive **newer than the previously-newest visible timestamp** in a buffer and show a centered "Show N new items" pill instead — clicking it flushes the buffer into the list:
|
|
191
|
+
|
|
192
|
+
```jsx
|
|
193
|
+
<Feed
|
|
194
|
+
items={liveActivity} // parent prepends as events stream in
|
|
195
|
+
newItemsBehavior="pill"
|
|
196
|
+
onNewItemsFlush={(flushed) => console.log(`released ${flushed.length} items`)}
|
|
197
|
+
highlightNew={30000} // flushed items carry a "New" tag for 30s
|
|
198
|
+
sortOptions={[{ value: "newest", label: "Newest first", field: "timestamp", direction: "desc" }]}
|
|
199
|
+
defaultSort="newest"
|
|
200
|
+
/>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Behavior details (all decided by the pure, exhaustively-tested `partitionNewItems` / `flushBuffer` kernel in `feedLiveBuffer.js`):
|
|
204
|
+
|
|
205
|
+
- The **first load never buffers** — there is no previous watermark.
|
|
206
|
+
- Only items **strictly newer** than the watermark buffer; equal timestamps and unparseable/missing timestamps stay visible.
|
|
207
|
+
- **Updates to already-visible items (matched by key) never buffer**, even if their timestamp moved forward — a visible row must not vanish into the pill. Give live items stable `id`s; index-based fallback keys defeat update detection.
|
|
208
|
+
- The pill renders even when the visible list is empty (the buffer may hold the only items), and the item count reflects visible items only.
|
|
209
|
+
- `highlightNew={ms}` marks flushed (pill) or freshly-prepended (immediate) items with an info `Tag` ("New") for that window, then clears automatically. `false` (default) disables it. Custom `renderItem` rows bypass the marker.
|
|
210
|
+
- Labels: `labels.newItems` (string or `(count) => string`) and `labels.newItemTag`.
|
|
211
|
+
|
|
212
|
+
`partitionNewItems(prevNewestTs, items, getTs, { knownIds, getId })`, `flushBuffer(visible, buffered, getTs)`, and `toTimestampMs(value)` are exported for server adapters and tests.
|
|
213
|
+
|
|
214
|
+
## Per-type presets
|
|
215
|
+
|
|
216
|
+
Stop repeating `iconName: "calling"` on every call row. `typePresets` maps `item.type` machine values to display defaults, merged **under** item-level values (the item always wins):
|
|
217
|
+
|
|
218
|
+
```jsx
|
|
219
|
+
import { Feed, DEFAULT_FEED_TYPE_PRESETS } from "hs-uix/feed";
|
|
220
|
+
|
|
221
|
+
// Built-in HubSpot activity-type presets:
|
|
222
|
+
<Feed items={engagements} typePresets /> // or typePresets={DEFAULT_FEED_TYPE_PRESETS}
|
|
223
|
+
|
|
224
|
+
// Extend or override:
|
|
225
|
+
<Feed
|
|
226
|
+
items={engagements}
|
|
227
|
+
typePresets={{
|
|
228
|
+
...DEFAULT_FEED_TYPE_PRESETS,
|
|
229
|
+
call: { ...DEFAULT_FEED_TYPE_PRESETS.call, statusVariant: "info" },
|
|
230
|
+
deploy: { icon: "rotate", label: "Deployment" },
|
|
231
|
+
}}
|
|
232
|
+
/>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
A preset fills these item keys when missing: `icon` → `iconName`, `color` → `iconColor`, `label` → `typeLabel`, `statusVariant` → `statusVariant`. Lookup by `type` is case-insensitive and snake_case-normalized, so API values like `"EMAIL"` or `"Postal Mail"` resolve.
|
|
236
|
+
|
|
237
|
+
`DEFAULT_FEED_TYPE_PRESETS` covers `call`, `email`, `incoming_email`, `forwarded_email`, `meeting`, `note`, `task`, `sms`, `whatsapp`, `linkedin_message`, `postal_mail`, and `conversation` — every icon name is verified against the native HubSpot icon whitelist by a unit test (invalid native icon names render **nothing**).
|
|
238
|
+
|
|
239
|
+
| Preset key | Description |
|
|
240
|
+
|---|---|
|
|
241
|
+
| `icon` | Native HubSpot icon name (verify against the whitelist — invalid names render nothing) |
|
|
242
|
+
| `color` | Icon color: native enum (`alert`/`warning`/`success`/`inherit`) or any CSS color (SVG fallback) |
|
|
243
|
+
| `label` | Display label used as `typeLabel` |
|
|
244
|
+
| `statusVariant` | `StatusTag` variant fallback: `default`, `info`, `success`, `warning`, `danger` |
|
|
245
|
+
|
|
186
246
|
## Server-side mode
|
|
187
247
|
|
|
188
248
|
Use `serverSide` when the parent/API owns filtering, sorting, searching, and pagination. Feed renders the toolbar and calls back with params, but does not mutate `items`.
|
|
@@ -223,4 +283,13 @@ Use `serverSide` when the parent/API owns filtering, sorting, searching, and pag
|
|
|
223
283
|
|
|
224
284
|
## Props
|
|
225
285
|
|
|
286
|
+
Live-append and preset props:
|
|
287
|
+
|
|
288
|
+
| Prop | Type | Default | Notes |
|
|
289
|
+
|---|---|---|---|
|
|
290
|
+
| `newItemsBehavior` | `"immediate" \| "pill"` | `"immediate"` | `"pill"` buffers strictly-newer arrivals behind a "Show N new items" Button (variant `secondary`, centered) |
|
|
291
|
+
| `onNewItemsFlush` | `(items) => void` | — | Fired with the released items when the pill is clicked |
|
|
292
|
+
| `highlightNew` | `number \| false` | `false` | Window (ms) during which flushed/prepended items carry an info `Tag` "New" marker; initial load is never marked |
|
|
293
|
+
| `typePresets` | `object \| true` | — | `{ [type]: { icon, color, label, statusVariant } }` merged under item values; `true` uses `DEFAULT_FEED_TYPE_PRESETS` |
|
|
294
|
+
|
|
226
295
|
See `feed.d.ts` for the full typed API.
|