hs-uix 1.6.4 → 1.7.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.
@@ -0,0 +1,367 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Primitives
5
+ // ---------------------------------------------------------------------------
6
+
7
+ export type KanbanStageVariant = "success" | "info" | "warning" | "default";
8
+ export type KanbanCardDensity = "compact" | "comfortable";
9
+ export type KanbanStageControlMode = "select" | "menu" | "none";
10
+ export type KanbanCardBodyMode = "descriptionList" | "stack";
11
+ export type KanbanFilterType = "select" | "multiselect" | "dateRange";
12
+
13
+ export interface KanbanOption<T = string> {
14
+ label: string;
15
+ value: T;
16
+ }
17
+
18
+ export interface KanbanFilterConfig<Row = Record<string, unknown>> {
19
+ name: string;
20
+ type?: KanbanFilterType;
21
+ placeholder?: string;
22
+ /** Prefix used in the active-filter chip. Defaults to `placeholder` or `name`. */
23
+ chipLabel?: string;
24
+ options?: KanbanOption[];
25
+ filterFn?: (row: Row, value: unknown) => boolean;
26
+ }
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Card fields
30
+ // ---------------------------------------------------------------------------
31
+
32
+ export type KanbanCardFieldPlacement = "title" | "subtitle" | "meta" | "body" | "footer";
33
+
34
+ export interface KanbanCardField<Row = Record<string, unknown>> {
35
+ key?: string;
36
+ field?: string;
37
+ label?: string;
38
+ render?: (value: unknown, row: Row) => ReactNode;
39
+ placement?: KanbanCardFieldPlacement;
40
+ href?:
41
+ | string
42
+ | { url: string; external?: boolean }
43
+ | ((row: Row) => string | { url: string; external?: boolean });
44
+ truncate?: true | number;
45
+ visible?: (row: Row) => boolean;
46
+ colSpan?: number;
47
+ }
48
+
49
+ export interface KanbanCardDividers {
50
+ afterTitle?: boolean;
51
+ afterSubtitle?: boolean;
52
+ afterBody?: boolean;
53
+ afterFooter?: boolean;
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Stage definition
58
+ // ---------------------------------------------------------------------------
59
+
60
+ export interface KanbanStageChangeResult {
61
+ extraProperties?: Record<string, unknown>;
62
+ }
63
+
64
+ export interface KanbanTransitionPromptContext<Row> {
65
+ row: Row;
66
+ fromStage: string;
67
+ toStage: string;
68
+ onConfirm: (result?: KanbanStageChangeResult) => void;
69
+ onCancel: () => void;
70
+ }
71
+
72
+ export interface KanbanStage<Row = Record<string, unknown>> {
73
+ value: string;
74
+ label: string;
75
+ shortLabel?: string;
76
+ description?: string;
77
+ variant?: KanbanStageVariant;
78
+ color?: string;
79
+ icon?: string;
80
+ terminal?: boolean;
81
+ order?: number;
82
+ footer?: (rows: Row[]) => ReactNode;
83
+ canEnter?: (row: Row) => boolean;
84
+ onEnterRequired?: {
85
+ render: (ctx: KanbanTransitionPromptContext<Row>) => ReactNode;
86
+ };
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Pagination
91
+ // ---------------------------------------------------------------------------
92
+
93
+ export interface KanbanStageMeta {
94
+ hasMore?: boolean;
95
+ totalCount?: number;
96
+ loading?: boolean;
97
+ error?: string;
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Sorting
102
+ // ---------------------------------------------------------------------------
103
+
104
+ export interface KanbanSortOption<Row = Record<string, unknown>> {
105
+ value: string;
106
+ label: string;
107
+ /** Optional grouping metadata for the richer "field + direction" sort picker UI. */
108
+ field?: string;
109
+ /** Optional direction used with `field` to render Asc/Desc toggle controls. */
110
+ direction?: "asc" | "desc";
111
+ /** Optional label shown for the grouped field selector. */
112
+ fieldLabel?: string;
113
+ comparator: (a: Row, b: Row) => number;
114
+ }
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // Selection
118
+ // ---------------------------------------------------------------------------
119
+
120
+ export interface KanbanSelectionAction<Id = string | number> {
121
+ label: string;
122
+ icon?: string;
123
+ variant?: "primary" | "secondary" | "transparent";
124
+ onClick: (selectedIds: Id[]) => void;
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Render contexts
129
+ // ---------------------------------------------------------------------------
130
+
131
+ export interface KanbanCardRenderContext<Row = Record<string, unknown>> {
132
+ stage: KanbanStage<Row>;
133
+ isChanging: boolean;
134
+ density: KanbanCardDensity;
135
+ onStageChange: (newStage: string) => void;
136
+ }
137
+
138
+ export interface KanbanParams {
139
+ search: string;
140
+ filters: Record<string, unknown>;
141
+ sort: string | null;
142
+ collapsedStages: string[];
143
+ }
144
+
145
+ export interface KanbanEmptyStateRenderContext {
146
+ title: string;
147
+ message: string;
148
+ }
149
+
150
+ export interface KanbanLoadingStateRenderContext {
151
+ label: string;
152
+ }
153
+
154
+ export interface KanbanErrorStateRenderContext {
155
+ error: string | boolean;
156
+ title: string;
157
+ message: string;
158
+ }
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // Labels
162
+ // ---------------------------------------------------------------------------
163
+
164
+ export interface KanbanLabels {
165
+ search?: string;
166
+ showMore?: (shown: number, total: number) => string;
167
+ showLess?: string;
168
+ loadMore?: (loaded: number, total?: number) => string;
169
+ loadingMore?: string;
170
+ retryLoadMore?: string;
171
+ emptyColumn?: string;
172
+ emptyTitle?: string;
173
+ emptyMessage?: string;
174
+ loading?: string;
175
+ loadingMessage?: string;
176
+ errorTitle?: string;
177
+ errorMessage?: string;
178
+ cardCount?: (n: number) => string;
179
+ moveTo?: string;
180
+ clearAll?: string;
181
+ selectAll?: string | ((count: number, label: string) => string);
182
+ deselectAll?: string;
183
+ selected?: (count: number, label: string) => string;
184
+ /** Primary label for the overflow-filter toggle button. */
185
+ filtersButton?: string;
186
+ dateFrom?: string;
187
+ dateTo?: string;
188
+ sortButton?: string;
189
+ sortAscending?: string;
190
+ sortDescending?: string;
191
+ metricsButton?: string;
192
+ }
193
+
194
+ export interface KanbanMetricItem {
195
+ id?: string;
196
+ label: string;
197
+ number: string | number;
198
+ trend?: {
199
+ direction?: "increase" | "decrease";
200
+ value: string;
201
+ color?: "red" | "green";
202
+ };
203
+ }
204
+
205
+ export interface KanbanSelectionBarRenderContext<Id = string | number> {
206
+ selectedIds: Id[];
207
+ selectedCount: number;
208
+ displayCount: number;
209
+ countLabel: (n: number) => string;
210
+ allSelected: boolean;
211
+ onSelectAll: () => void;
212
+ onDeselectAll: () => void;
213
+ selectionActions: KanbanSelectionAction<Id>[];
214
+ labels: KanbanLabels;
215
+ }
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // KanbanCardActions — shipped helper
219
+ // ---------------------------------------------------------------------------
220
+
221
+ export interface KanbanCardAction {
222
+ key?: string;
223
+ label: string;
224
+ icon?: string;
225
+ tooltip?: string;
226
+ variant?: "primary" | "secondary" | "transparent";
227
+ disabled?: boolean;
228
+ visible?: boolean;
229
+ onClick?: () => void;
230
+ href?: string | { url: string; external?: boolean };
231
+ }
232
+
233
+ export interface KanbanCardActionsProps {
234
+ actions: KanbanCardAction[];
235
+ display?: "icon" | "label" | "iconAndLabel";
236
+ size?: "xs" | "sm";
237
+ align?: "start" | "end" | "between";
238
+ gap?: string;
239
+ separator?: "none" | "pipe";
240
+ overflowAfter?: number;
241
+ overflowLabel?: string;
242
+ }
243
+
244
+ export declare function KanbanCardActions(props: KanbanCardActionsProps): ReactElement | null;
245
+
246
+ // ---------------------------------------------------------------------------
247
+ // Kanban props
248
+ // ---------------------------------------------------------------------------
249
+
250
+ export interface KanbanProps<Row = Record<string, unknown>, Id = string | number> {
251
+ // Data
252
+ data: Row[];
253
+ stages: KanbanStage<Row>[];
254
+ groupBy?: string | ((row: Row) => string);
255
+ rowIdField?: string;
256
+
257
+ // Card rendering
258
+ renderCard?: (row: Row, context: KanbanCardRenderContext<Row>) => ReactNode;
259
+ cardFields?: KanbanCardField<Row>[];
260
+ cardDensity?: KanbanCardDensity;
261
+ cardDividers?: boolean | KanbanCardDividers;
262
+ cardBodyAs?: KanbanCardBodyMode;
263
+ countDisplay?: "tag" | "text" | "none";
264
+ maxBodyLines?: number;
265
+ maxCardsPerColumn?: number;
266
+ maxCardsExpanded?: number;
267
+ expandedStages?: string[];
268
+ onExpandedStagesChange?: (stages: string[]) => void;
269
+
270
+ // Per-stage pagination
271
+ stageMeta?: Record<string, KanbanStageMeta>;
272
+ onLoadMore?: (stage: string) => void;
273
+
274
+ // Selection
275
+ selectable?: boolean;
276
+ selectedIds?: Id[];
277
+ onSelectionChange?: (selectedIds: Id[]) => void;
278
+ selectionActions?: KanbanSelectionAction<Id>[];
279
+ recordLabel?: { singular: string; plural: string };
280
+ /** Optional key that forces uncontrolled selection memory to reset when it changes. */
281
+ selectionResetKey?: unknown;
282
+ /** Clears uncontrolled selection when search/filter/sort changes. Default true. */
283
+ resetSelectionOnQueryChange?: boolean;
284
+ /** Hide the default selection bar while keeping selection state active. */
285
+ showSelectionBar?: boolean;
286
+ /** Full render override for the selection bar — receives all state + handlers. */
287
+ renderSelectionBar?: (ctx: KanbanSelectionBarRenderContext<Id>) => ReactNode;
288
+
289
+ // Stage transitions
290
+ stageControl?: KanbanStageControlMode;
291
+ stageControlPlacement?: "inline" | "separateRow";
292
+ onStageChange?: (
293
+ row: Row,
294
+ newStage: string,
295
+ oldStage: string,
296
+ result?: KanbanStageChangeResult
297
+ ) => void | Promise<unknown>;
298
+ isStageChanging?: (row: Row) => boolean;
299
+ canMove?: (row: Row, toStage: string) => boolean;
300
+
301
+ // Toolbar
302
+ showSearch?: boolean;
303
+ /** Search is only active, and the search input only renders, when this list is non-empty. */
304
+ searchFields?: string[];
305
+ searchPlaceholder?: string;
306
+ /** ms to debounce onSearchChange callback. 0 = no debounce. */
307
+ searchDebounce?: number;
308
+ /** Enable fuzzy matching via Fuse.js */
309
+ fuzzySearch?: boolean;
310
+ /** Custom Fuse.js options (threshold, distance, keys, etc.) */
311
+ fuzzyOptions?: Record<string, unknown>;
312
+ filters?: KanbanFilterConfig<Row>[];
313
+ /** Number of filters shown inline before the "Filters" overflow button. Default 2. */
314
+ filterInlineLimit?: number;
315
+ /** Show active filter chips with individual clear affordances. Default true. */
316
+ showFilterBadges?: boolean;
317
+ /** Show the "Clear all" reset button when filters are active. Defaults to `showFilterBadges` when omitted, so hiding the chips hides the clear-all button too. */
318
+ showClearFiltersButton?: boolean;
319
+ /** Supports simple options or grouped field/direction options for the richer sort picker UI. */
320
+ sortOptions?: KanbanSortOption<Row>[];
321
+ defaultSort?: string;
322
+ sort?: string;
323
+ onSortChange?: (value: string) => void;
324
+
325
+ // Column level
326
+ columnFooter?: (rows: Row[], stage: KanbanStage<Row>) => ReactNode;
327
+ /**
328
+ * Pixel width for each column, passed to AutoGrid's columnWidth. Columns
329
+ * share available horizontal space equally with this value as the minimum.
330
+ * Clamped to 280px minimum regardless of the value passed. Default 280px.
331
+ */
332
+ columnWidth?: number;
333
+ collapsedStages?: string[];
334
+ onCollapsedStagesChange?: (stages: string[]) => void;
335
+
336
+ // Metrics panel (toggled via the toolbar's Metrics button)
337
+ /** Array of stat items for the <Statistics> panel, or a custom ReactNode. */
338
+ metrics?: KanbanMetricItem[] | ReactNode;
339
+ /** Controlled visibility of the metrics panel. */
340
+ showMetrics?: boolean;
341
+ /** Fires when the Metrics button is clicked. Receives the new visible state. */
342
+ onMetricsToggle?: (visible: boolean) => void;
343
+
344
+ // Controlled state
345
+ searchValue?: string;
346
+ onSearchChange?: (term: string) => void;
347
+ filterValues?: Record<string, unknown>;
348
+ onFilterChange?: (values: Record<string, unknown>) => void;
349
+ /** Unified callback fired when search/filter/sort/collapsed-stage state changes. */
350
+ onParamsChange?: (params: KanbanParams) => void;
351
+ loading?: boolean;
352
+ error?: string | boolean;
353
+
354
+ // Labels
355
+ labels?: KanbanLabels;
356
+
357
+ /** Replace the default empty-state UI. */
358
+ renderEmptyState?: (ctx: KanbanEmptyStateRenderContext) => ReactNode;
359
+ /** Replace the default loading spinner UI. */
360
+ renderLoadingState?: (ctx: KanbanLoadingStateRenderContext) => ReactNode;
361
+ /** Replace the default top-level error UI. */
362
+ renderErrorState?: (ctx: KanbanErrorStateRenderContext) => ReactNode;
363
+ }
364
+
365
+ export declare function Kanban<Row = Record<string, unknown>, Id = string | number>(
366
+ props: KanbanProps<Row, Id>
367
+ ): ReactElement | null;
package/utils.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ import type { ComponentType } from "react";
2
+ import type { DataTableProps, DataTableColumn } from "./packages/datatable/index";
3
+ import type { KanbanProps } from "./packages/kanban/index";
4
+
1
5
  export type AutoStatusTagVariant = "default" | "success" | "warning" | "danger" | "info";
2
6
  export type AutoTagVariant = "default" | "success" | "warning" | "error" | "info";
3
7
 
@@ -47,6 +51,17 @@ export interface StatusTagSortComparatorOptions extends AutoStatusTagOptions {
47
51
  export declare function createStatusTagSortComparator(
48
52
  options?: StatusTagSortComparatorOptions
49
53
  ): (aValue: unknown, bValue: unknown) => number;
54
+ export declare function buildCrmSearchConfig(params?: CrmSearchParams, options?: CrmSearchConfigOptions): Record<string, unknown>;
55
+ export declare function normalizeCrmSearchRecord<Row = Record<string, unknown>>(record: unknown, options?: CrmSearchNormalizeOptions<Row>): Row;
56
+ export declare function normalizeCrmSearchRows<Row = Record<string, unknown>>(response: unknown, options?: CrmSearchNormalizeOptions<Row>): Row[];
57
+ export declare function useCrmSearchDataSource<Row = Record<string, unknown>>(params?: CrmSearchParams, options?: CrmSearchConfigOptions<Row>): CrmSearchDataSource<Row>;
58
+ export declare function crmSearchResultToOption<Row = Record<string, unknown>>(row: Row, options?: CrmSearchOptionOptions<Row>): BuiltOption;
59
+ export declare function useCrmSearchOptions<Row = Record<string, unknown>>(params?: CrmSearchParams, options?: CrmSearchConfigOptions<Row> & CrmSearchOptionOptions<Row>): CrmSearchOptionsDataSource<Row>;
60
+ export declare function makeCrmSearchSelectField<Field = Record<string, unknown>>(field: Field, searchOptions: { options?: BuiltOption[]; loading?: boolean; isLoading?: boolean }): Field & CrmSearchFormField;
61
+ export declare function makeCrmSearchMultiSelectField<Field = Record<string, unknown>>(field: Field, searchOptions: { options?: BuiltOption[]; loading?: boolean; isLoading?: boolean }): Field & CrmSearchFormField;
62
+ export declare function resolveCrmObjectType(objectType: string): string;
63
+ export declare const CrmDataTable: ComponentType<CrmDataTableProps>;
64
+ export declare const CrmKanban: ComponentType<CrmKanbanProps>;
50
65
  export interface FormatCurrencyCompactOptions extends Intl.NumberFormatOptions {
51
66
  locale?: string;
52
67
  currency?: string;
@@ -54,6 +69,113 @@ export interface FormatCurrencyCompactOptions extends Intl.NumberFormatOptions {
54
69
  compactDisplay?: "short" | "long";
55
70
  }
56
71
 
72
+ export interface CrmSearchParams {
73
+ search?: string;
74
+ filters?: Record<string, unknown>;
75
+ sort?: unknown;
76
+ pageLength?: number;
77
+ [key: string]: unknown;
78
+ }
79
+
80
+ export interface CrmSearchConfigOptions<Row = Record<string, unknown>> {
81
+ objectType?: string;
82
+ properties?: string[];
83
+ query?: string;
84
+ filterGroups?: Array<{ filters: Array<Record<string, unknown>> }>;
85
+ sorts?: Array<Record<string, unknown>>;
86
+ pageLength?: number;
87
+ propertyMap?: Record<string, string>;
88
+ filterMap?: (filters: Record<string, unknown>, params: CrmSearchParams) => Array<{ filters: Array<Record<string, unknown>> }> | undefined;
89
+ sortMap?: (sort: unknown, params: CrmSearchParams) => Array<Record<string, unknown>> | undefined;
90
+ baseConfig?: Record<string, unknown>;
91
+ format?: Record<string, unknown>;
92
+ row?: CrmSearchNormalizeOptions<Row>;
93
+ rowIdField?: string;
94
+ option?: CrmSearchOptionOptions<Row>;
95
+ mapResponse?: (response: unknown) => Row[];
96
+ totalCount?: number | ((response: unknown) => number);
97
+ loading?: boolean | ((response: unknown) => boolean);
98
+ error?: string | boolean | ((response: unknown) => string | boolean);
99
+ }
100
+
101
+ export interface CrmSearchNormalizeOptions<Row = Record<string, unknown>> {
102
+ idField?: string;
103
+ objectIdField?: string;
104
+ propertiesKey?: string;
105
+ flattenProperties?: boolean;
106
+ propertyValueKey?: string;
107
+ mapRecord?: (record: unknown) => Row;
108
+ }
109
+
110
+ export interface CrmSearchOptionOptions<Row = Record<string, unknown>> {
111
+ label?: string | ((row: Row) => unknown);
112
+ value?: string | ((row: Row) => unknown);
113
+ description?: string | ((row: Row) => unknown);
114
+ fallbackLabel?: string;
115
+ mapOption?: (row: Row) => BuiltOption;
116
+ }
117
+
118
+ export interface CrmSearchDataSource<Row = Record<string, unknown>> {
119
+ data: Row[];
120
+ rows: Row[];
121
+ response: unknown;
122
+ loading: boolean;
123
+ isLoading: boolean;
124
+ error: string | boolean;
125
+ totalCount: number;
126
+ rowIdField: string;
127
+ }
128
+
129
+ export interface CrmSearchOptionsDataSource<Row = Record<string, unknown>> extends CrmSearchDataSource<Row> {
130
+ options: BuiltOption[];
131
+ }
132
+
133
+ export interface CrmSearchFormField<Field = Record<string, unknown>> extends Record<string, unknown> {
134
+ type: "select" | "multiselect";
135
+ options: BuiltOption[];
136
+ loading?: boolean;
137
+ }
138
+
139
+ export interface CrmDataTableProps<Row = Record<string, unknown>> extends Omit<DataTableProps<Row>, "data" | "loading" | "error" | "columns" | "searchValue" | "onParamsChange"> {
140
+ objectType: "contact" | "contacts" | "company" | "companies" | "deal" | "deals" | string;
141
+ properties?: string[];
142
+ columns?: DataTableColumn<Row>[];
143
+ pageLength?: number;
144
+ serverSide?: boolean;
145
+ filters?: DataTableProps<Row>["filters"];
146
+ autoFilters?: boolean | string[] | { fields?: string[] };
147
+ autoFilterMaxOptions?: number;
148
+ filterMap?: CrmSearchConfigOptions<Row>["filterMap"];
149
+ propertyMap?: Record<string, string>;
150
+ sortMap?: CrmSearchConfigOptions<Row>["sortMap"];
151
+ searchFields?: string[];
152
+ format?: Record<string, unknown>;
153
+ mapRecord?: (record: unknown) => Row;
154
+ dataTableProps?: Partial<DataTableProps<Row>>;
155
+ }
156
+
157
+ export interface CrmKanbanProps<Row = Record<string, unknown>>
158
+ extends Omit<KanbanProps<Row>, "data" | "loading" | "error" | "stages" | "groupBy" | "searchValue" | "filterValues" | "onParamsChange"> {
159
+ objectType: "contact" | "contacts" | "company" | "companies" | "deal" | "deals" | string;
160
+ properties?: string[];
161
+ groupBy: KanbanProps<Row>["groupBy"];
162
+ /** Pass for real pipeline labels; auto-derived from the batch when omitted. */
163
+ stages?: KanbanProps<Row>["stages"];
164
+ /** Friendly labels for auto-derived stages — object map or fn. */
165
+ stageLabels?: Record<string, string> | ((value: string) => string);
166
+ pageLength?: number;
167
+ serverSide?: boolean;
168
+ autoFilters?: boolean | string[] | { fields?: string[] };
169
+ autoFilterMaxOptions?: number;
170
+ filterMap?: CrmSearchConfigOptions<Row>["filterMap"];
171
+ propertyMap?: Record<string, string>;
172
+ sortMap?: CrmSearchConfigOptions<Row>["sortMap"];
173
+ searchFields?: string[];
174
+ format?: Record<string, unknown>;
175
+ mapRecord?: (record: unknown) => Row;
176
+ kanbanProps?: Partial<KanbanProps<Row>>;
177
+ }
178
+
57
179
  export interface DeriveCardFieldsOptions<Row = Record<string, unknown>> {
58
180
  titleField?: string;
59
181
  titleHref?: (row: Row) => string | { url: string; external?: boolean };