@wealthx/shadcn 1.5.15 → 1.5.17
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/.turbo/turbo-build.log +110 -110
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-AKWN5ZQG.mjs → chunk-A43XIVO6.mjs} +57 -29
- package/dist/{chunk-ZA44WICP.mjs → chunk-FGHM34AV.mjs} +38 -31
- package/dist/components/ui/kanban-column.js +123 -43
- package/dist/components/ui/kanban-column.mjs +2 -1
- package/dist/components/ui/pipeline-board.js +173 -86
- package/dist/components/ui/pipeline-board.mjs +3 -2
- package/dist/index.js +87 -58
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/src/components/ui/kanban-column.tsx +78 -37
- package/src/components/ui/pipeline-board.tsx +73 -49
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { MoreVertical } from "lucide-react";
|
|
2
|
+
import { MoreVertical, Plus } from "lucide-react";
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
4
|
import { useThemeVars } from "@/lib/theme-provider";
|
|
5
5
|
import { formatCurrency } from "@/lib/format-currency";
|
|
@@ -15,6 +15,12 @@ import {
|
|
|
15
15
|
} from "@/components/ui/dropdown-menu";
|
|
16
16
|
import { LeadCard, OpportunityCard } from "@/components/ui/opportunity-card";
|
|
17
17
|
import type { OpportunityCardProps } from "@/components/ui/opportunity-card";
|
|
18
|
+
import {
|
|
19
|
+
Tooltip,
|
|
20
|
+
TooltipContent,
|
|
21
|
+
TooltipProvider,
|
|
22
|
+
TooltipTrigger,
|
|
23
|
+
} from "@/components/ui/tooltip";
|
|
18
24
|
|
|
19
25
|
/**
|
|
20
26
|
* KanbanColumn — WealthX DS (L4 Section)
|
|
@@ -52,6 +58,12 @@ export interface KanbanColumnStage {
|
|
|
52
58
|
* Defaults to `"var(--color-border)"`.
|
|
53
59
|
*/
|
|
54
60
|
accentColor?: string | null;
|
|
61
|
+
/**
|
|
62
|
+
* Optional color for the stage count number in the column header.
|
|
63
|
+
* Pass a CSS variable string: `"var(--color-destructive)"`.
|
|
64
|
+
* Defaults to `text-muted-foreground`.
|
|
65
|
+
*/
|
|
66
|
+
countColor?: string | null;
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
export interface KanbanColumnProps {
|
|
@@ -91,6 +103,11 @@ export interface KanbanColumnProps {
|
|
|
91
103
|
onEditColumn?: () => void;
|
|
92
104
|
/** Only shown for non-default columns. */
|
|
93
105
|
onDeleteColumn?: () => void;
|
|
106
|
+
/**
|
|
107
|
+
* When provided, renders a `+` icon button in the column header.
|
|
108
|
+
* Intended for the Leads column — opens the Add Lead flow.
|
|
109
|
+
*/
|
|
110
|
+
onAddLead?: () => void;
|
|
94
111
|
|
|
95
112
|
// ── Card callbacks (forwarded to each OpportunityCard) ───────
|
|
96
113
|
onTaskToggle?: (opportunityId: string, taskId: string) => void;
|
|
@@ -115,10 +132,6 @@ export interface KanbanColumnProps {
|
|
|
115
132
|
// Helpers
|
|
116
133
|
// ---------------------------------------------------------------------------
|
|
117
134
|
|
|
118
|
-
function formatTotalValue(value: number): string {
|
|
119
|
-
return formatCurrency(value);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
135
|
function growthColor(growth: number): string {
|
|
123
136
|
// Use darkened text tokens — full-saturation tokens (#4CAF50, #F44336) fail 4.5:1 on white bg
|
|
124
137
|
return growth > 0
|
|
@@ -142,6 +155,7 @@ export function KanbanColumn({
|
|
|
142
155
|
loaderRef,
|
|
143
156
|
onEditColumn,
|
|
144
157
|
onDeleteColumn,
|
|
158
|
+
onAddLead,
|
|
145
159
|
onTaskToggle,
|
|
146
160
|
onMarkAsDone,
|
|
147
161
|
onMoveToNextStage,
|
|
@@ -203,41 +217,68 @@ export function KanbanColumn({
|
|
|
203
217
|
{/* Title row */}
|
|
204
218
|
<div className="flex items-center justify-between gap-2">
|
|
205
219
|
<h2 className="text-sm font-semibold text-foreground">
|
|
206
|
-
<span
|
|
220
|
+
<span
|
|
221
|
+
className={cn(!stage.countColor && "text-muted-foreground")}
|
|
222
|
+
style={stage.countColor ? { color: stage.countColor } : undefined}
|
|
223
|
+
>
|
|
224
|
+
{stage.count}
|
|
225
|
+
</span>{" "}
|
|
207
226
|
{stage.name}
|
|
208
227
|
</h2>
|
|
209
228
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
{onEditColumn && (
|
|
223
|
-
<DropdownMenuItem onClick={onEditColumn}>
|
|
224
|
-
Edit column settings
|
|
225
|
-
</DropdownMenuItem>
|
|
226
|
-
)}
|
|
227
|
-
{!isDefault && onDeleteColumn && (
|
|
228
|
-
<>
|
|
229
|
-
{onEditColumn && <DropdownMenuSeparator />}
|
|
230
|
-
<DropdownMenuItem
|
|
231
|
-
onClick={onDeleteColumn}
|
|
232
|
-
className="text-destructive focus:text-destructive"
|
|
229
|
+
<div className="flex items-center gap-0.5">
|
|
230
|
+
{onAddLead && (
|
|
231
|
+
<TooltipProvider>
|
|
232
|
+
<Tooltip>
|
|
233
|
+
<TooltipTrigger asChild>
|
|
234
|
+
<button
|
|
235
|
+
className={cn(
|
|
236
|
+
buttonVariants({ variant: "ghost", size: "icon" }),
|
|
237
|
+
"size-7 shrink-0",
|
|
238
|
+
)}
|
|
239
|
+
onClick={onAddLead}
|
|
240
|
+
aria-label="Add lead"
|
|
233
241
|
>
|
|
234
|
-
|
|
242
|
+
<Plus className="size-4" />
|
|
243
|
+
</button>
|
|
244
|
+
</TooltipTrigger>
|
|
245
|
+
<TooltipContent>Add lead</TooltipContent>
|
|
246
|
+
</Tooltip>
|
|
247
|
+
</TooltipProvider>
|
|
248
|
+
)}
|
|
249
|
+
|
|
250
|
+
{hasMenu && (
|
|
251
|
+
<DropdownMenu>
|
|
252
|
+
<DropdownMenuTrigger
|
|
253
|
+
className={cn(
|
|
254
|
+
buttonVariants({ variant: "ghost", size: "icon" }),
|
|
255
|
+
"-mr-1 size-7 shrink-0",
|
|
256
|
+
)}
|
|
257
|
+
aria-label="Column actions"
|
|
258
|
+
>
|
|
259
|
+
<MoreVertical className="size-4" />
|
|
260
|
+
</DropdownMenuTrigger>
|
|
261
|
+
<DropdownMenuContent align="end">
|
|
262
|
+
{onEditColumn && (
|
|
263
|
+
<DropdownMenuItem onClick={onEditColumn}>
|
|
264
|
+
Edit column settings
|
|
235
265
|
</DropdownMenuItem>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
266
|
+
)}
|
|
267
|
+
{!isDefault && onDeleteColumn && (
|
|
268
|
+
<>
|
|
269
|
+
{onEditColumn && <DropdownMenuSeparator />}
|
|
270
|
+
<DropdownMenuItem
|
|
271
|
+
onClick={onDeleteColumn}
|
|
272
|
+
className="text-destructive focus:text-destructive"
|
|
273
|
+
>
|
|
274
|
+
Delete column
|
|
275
|
+
</DropdownMenuItem>
|
|
276
|
+
</>
|
|
277
|
+
)}
|
|
278
|
+
</DropdownMenuContent>
|
|
279
|
+
</DropdownMenu>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
241
282
|
</div>
|
|
242
283
|
|
|
243
284
|
{/* Stats row */}
|
|
@@ -253,8 +294,8 @@ export function KanbanColumn({
|
|
|
253
294
|
) : (
|
|
254
295
|
<span />
|
|
255
296
|
)}
|
|
256
|
-
<span className="text-
|
|
257
|
-
{
|
|
297
|
+
<span className="text-sm font-bold tabular-nums text-foreground">
|
|
298
|
+
{formatCurrency(stage.totalValue)}
|
|
258
299
|
</span>
|
|
259
300
|
</div>
|
|
260
301
|
</div>
|
|
@@ -5,10 +5,7 @@ import { cn } from "@/lib/utils";
|
|
|
5
5
|
import { Input } from "@/components/ui/input";
|
|
6
6
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
|
7
7
|
import { KanbanColumn } from "@/components/ui/kanban-column";
|
|
8
|
-
import type {
|
|
9
|
-
KanbanColumnProps,
|
|
10
|
-
KanbanColumnStage,
|
|
11
|
-
} from "@/components/ui/kanban-column";
|
|
8
|
+
import type { KanbanColumnStage } from "@/components/ui/kanban-column";
|
|
12
9
|
import type { OpportunityCardProps } from "@/components/ui/opportunity-card";
|
|
13
10
|
|
|
14
11
|
/**
|
|
@@ -53,10 +50,21 @@ export interface PipelineBoardColumn {
|
|
|
53
50
|
isDropTarget?: boolean;
|
|
54
51
|
/** Whether this is a fixed/system column (no Delete in menu). */
|
|
55
52
|
isDefault?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* When true, this column is rendered in a fixed left section and does not
|
|
55
|
+
* scroll horizontally with the rest of the board. Use for the High Priority
|
|
56
|
+
* column, which must always be visible.
|
|
57
|
+
*/
|
|
58
|
+
isPinned?: boolean;
|
|
56
59
|
isLoading?: boolean;
|
|
57
60
|
isLoadingMore?: boolean;
|
|
58
61
|
hasMore?: boolean;
|
|
59
62
|
loaderRef?: React.Ref<HTMLDivElement>;
|
|
63
|
+
/**
|
|
64
|
+
* When provided, renders a `+` icon button in the column header.
|
|
65
|
+
* Intended for the Leads column — opens the Add Lead flow.
|
|
66
|
+
*/
|
|
67
|
+
onAddLead?: () => void;
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
export interface PipelineBoardProps {
|
|
@@ -78,8 +86,6 @@ export interface PipelineBoardProps {
|
|
|
78
86
|
// ── Column callbacks ─────────────────────────────────────────
|
|
79
87
|
onEditColumn?: (stageId: string) => void;
|
|
80
88
|
onDeleteColumn?: (stageId: string) => void;
|
|
81
|
-
|
|
82
|
-
// ── Toolbar ──────────────────────────────────────────────────
|
|
83
89
|
onRefresh?: () => void;
|
|
84
90
|
|
|
85
91
|
// ── Card callbacks ───────────────────────────────────────────
|
|
@@ -217,6 +223,48 @@ export function PipelineBoard({
|
|
|
217
223
|
const hasToolbar =
|
|
218
224
|
onSearchChange || (filterOptions.length > 0 && onFilterChange);
|
|
219
225
|
|
|
226
|
+
const pinnedCols = columns.filter((c) => c.isPinned);
|
|
227
|
+
const scrollableCols = columns.filter((c) => !c.isPinned);
|
|
228
|
+
|
|
229
|
+
const renderColumn = (col: PipelineBoardColumn) => (
|
|
230
|
+
<KanbanColumn
|
|
231
|
+
key={col.key}
|
|
232
|
+
stage={col.stage}
|
|
233
|
+
opportunities={col.opportunities}
|
|
234
|
+
isDragging={col.isDragging}
|
|
235
|
+
isDropTarget={col.isDropTarget}
|
|
236
|
+
isDefault={col.isDefault}
|
|
237
|
+
isLoading={col.isLoading}
|
|
238
|
+
isLoadingMore={col.isLoadingMore}
|
|
239
|
+
hasMore={col.hasMore}
|
|
240
|
+
loaderRef={col.loaderRef}
|
|
241
|
+
onEditColumn={
|
|
242
|
+
!col.isPinned && onEditColumn
|
|
243
|
+
? () => onEditColumn(col.stage.id)
|
|
244
|
+
: undefined
|
|
245
|
+
}
|
|
246
|
+
onDeleteColumn={
|
|
247
|
+
onDeleteColumn && !col.isDefault && !col.isPinned
|
|
248
|
+
? () => onDeleteColumn(col.stage.id)
|
|
249
|
+
: undefined
|
|
250
|
+
}
|
|
251
|
+
onCardDrop={
|
|
252
|
+
onMoveCard ? (cardId) => onMoveCard(cardId, col.key) : undefined
|
|
253
|
+
}
|
|
254
|
+
onCardClick={onCardClick}
|
|
255
|
+
onTaskToggle={onTaskToggle}
|
|
256
|
+
onMarkAsDone={onMarkAsDone}
|
|
257
|
+
onMoveToNextStage={onMoveToNextStage}
|
|
258
|
+
onSendLoanApplication={col.onSendLoanApplication}
|
|
259
|
+
onViewDetails={onViewDetails}
|
|
260
|
+
onChangePriority={onChangePriority}
|
|
261
|
+
onPutOnHold={onPutOnHold}
|
|
262
|
+
onDeleteOpportunity={onDeleteOpportunity}
|
|
263
|
+
onAddLead={col.onAddLead}
|
|
264
|
+
submittingOpportunityId={submittingOpportunityId}
|
|
265
|
+
/>
|
|
266
|
+
);
|
|
267
|
+
|
|
220
268
|
return (
|
|
221
269
|
<div
|
|
222
270
|
className={cn("flex h-full flex-col bg-muted/20", className)}
|
|
@@ -234,51 +282,27 @@ export function PipelineBoard({
|
|
|
234
282
|
/>
|
|
235
283
|
)}
|
|
236
284
|
|
|
237
|
-
{/* ── Board
|
|
238
|
-
<div className="flex flex-1
|
|
239
|
-
{columns
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
opportunities={col.opportunities}
|
|
244
|
-
isDragging={col.isDragging}
|
|
245
|
-
isDropTarget={col.isDropTarget}
|
|
246
|
-
isDefault={col.isDefault}
|
|
247
|
-
isLoading={col.isLoading}
|
|
248
|
-
isLoadingMore={col.isLoadingMore}
|
|
249
|
-
hasMore={col.hasMore}
|
|
250
|
-
loaderRef={col.loaderRef}
|
|
251
|
-
onEditColumn={
|
|
252
|
-
onEditColumn ? () => onEditColumn(col.stage.id) : undefined
|
|
253
|
-
}
|
|
254
|
-
onDeleteColumn={
|
|
255
|
-
onDeleteColumn && !col.isDefault
|
|
256
|
-
? () => onDeleteColumn(col.stage.id)
|
|
257
|
-
: undefined
|
|
258
|
-
}
|
|
259
|
-
onCardDrop={
|
|
260
|
-
onMoveCard ? (cardId) => onMoveCard(cardId, col.key) : undefined
|
|
261
|
-
}
|
|
262
|
-
onCardClick={onCardClick}
|
|
263
|
-
onTaskToggle={onTaskToggle}
|
|
264
|
-
onMarkAsDone={onMarkAsDone}
|
|
265
|
-
onMoveToNextStage={onMoveToNextStage}
|
|
266
|
-
onSendLoanApplication={col.onSendLoanApplication}
|
|
267
|
-
onViewDetails={onViewDetails}
|
|
268
|
-
onChangePriority={onChangePriority}
|
|
269
|
-
onPutOnHold={onPutOnHold}
|
|
270
|
-
onDeleteOpportunity={onDeleteOpportunity}
|
|
271
|
-
submittingOpportunityId={submittingOpportunityId}
|
|
272
|
-
/>
|
|
273
|
-
))}
|
|
274
|
-
|
|
275
|
-
{columns.length === 0 && (
|
|
276
|
-
<div className="flex flex-1 items-center justify-center">
|
|
277
|
-
<p className="text-sm text-muted-foreground">
|
|
278
|
-
No columns to display.
|
|
279
|
-
</p>
|
|
285
|
+
{/* ── Board area ── */}
|
|
286
|
+
<div className="flex flex-1 overflow-hidden">
|
|
287
|
+
{/* Pinned columns — always visible, do not scroll */}
|
|
288
|
+
{pinnedCols.length > 0 && (
|
|
289
|
+
<div className="flex shrink-0 gap-3 border-r border-border p-4">
|
|
290
|
+
{pinnedCols.map(renderColumn)}
|
|
280
291
|
</div>
|
|
281
292
|
)}
|
|
293
|
+
|
|
294
|
+
{/* Scrollable columns */}
|
|
295
|
+
<div className="flex flex-1 gap-3 overflow-x-auto p-4">
|
|
296
|
+
{scrollableCols.map(renderColumn)}
|
|
297
|
+
|
|
298
|
+
{columns.length === 0 && (
|
|
299
|
+
<div className="flex flex-1 items-center justify-center">
|
|
300
|
+
<p className="text-sm text-muted-foreground">
|
|
301
|
+
No columns to display.
|
|
302
|
+
</p>
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
</div>
|
|
282
306
|
</div>
|
|
283
307
|
</div>
|
|
284
308
|
);
|