@wealthx/shadcn 1.5.0 → 1.5.2

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.
Files changed (36) hide show
  1. package/.turbo/turbo-build.log +119 -119
  2. package/CHANGELOG.md +12 -0
  3. package/dist/chunk-G2EWIP2N.mjs +960 -0
  4. package/dist/{chunk-MHHA7QGO.mjs → chunk-ODO6BUOF.mjs} +1 -1
  5. package/dist/chunk-PX4M67XQ.mjs +301 -0
  6. package/dist/{chunk-FYUSF5KO.mjs → chunk-QRVEI6J3.mjs} +1 -1
  7. package/dist/{chunk-42NEC57Y.mjs → chunk-RAKBWNQH.mjs} +272 -3
  8. package/dist/components/ui/{contact-alert-dialog.js → contact-alert-dialog/index.js} +1029 -593
  9. package/dist/components/ui/contact-alert-dialog/index.mjs +31 -0
  10. package/dist/components/ui/file-preview-dialog.js +407 -100
  11. package/dist/components/ui/file-preview-dialog.mjs +3 -1
  12. package/dist/components/ui/kanban-column.js +408 -113
  13. package/dist/components/ui/kanban-column.mjs +3 -2
  14. package/dist/components/ui/opportunity-card.js +383 -88
  15. package/dist/components/ui/opportunity-card.mjs +2 -1
  16. package/dist/components/ui/pipeline-board.js +424 -129
  17. package/dist/components/ui/pipeline-board.mjs +4 -3
  18. package/dist/index.js +3081 -2282
  19. package/dist/index.mjs +39 -35
  20. package/dist/styles.css +1 -1
  21. package/package.json +6 -5
  22. package/src/components/index.tsx +3 -2
  23. package/src/components/ui/contact-alert-dialog/builder-ui.tsx +556 -0
  24. package/src/components/ui/contact-alert-dialog/config.ts +262 -0
  25. package/src/components/ui/contact-alert-dialog/contact-alert-dialog.tsx +214 -0
  26. package/src/components/ui/contact-alert-dialog/index.tsx +15 -0
  27. package/src/components/ui/contact-alert-dialog/types.ts +61 -0
  28. package/src/components/ui/contact-alert-dialog/utils.ts +93 -0
  29. package/src/components/ui/file-preview-dialog.tsx +299 -99
  30. package/src/components/ui/opportunity-card.tsx +328 -1
  31. package/src/styles/styles-css.ts +1 -1
  32. package/tsup.config.ts +1 -1
  33. package/dist/chunk-5WMFKQZ6.mjs +0 -180
  34. package/dist/chunk-Y24TXIFJ.mjs +0 -518
  35. package/dist/components/ui/contact-alert-dialog.mjs +0 -27
  36. package/src/components/ui/contact-alert-dialog.tsx +0 -710
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wealthx/shadcn",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -16,7 +16,8 @@
16
16
  "dependencies": {
17
17
  "@base-ui/react": "^1.3.0",
18
18
  "@fontsource-variable/figtree": "^5.2.10",
19
- "@tanstack/react-table": "^8.21.3",
19
+ "@react-awesome-query-builder/ui": "6.7.0-alpha.0",
20
+ "@tanstack/react-table": "8.21.3",
20
21
  "chart.js": "^4.5.1",
21
22
  "chartjs-plugin-datalabels": "^2.2.0",
22
23
  "class-variance-authority": "^0.7.1",
@@ -503,9 +504,9 @@
503
504
  "require": "./dist/components/ui/review-alerts-dialog.js"
504
505
  },
505
506
  "./contact-alert-dialog": {
506
- "types": "./src/components/ui/contact-alert-dialog.tsx",
507
- "import": "./dist/components/ui/contact-alert-dialog.mjs",
508
- "require": "./dist/components/ui/contact-alert-dialog.js"
507
+ "types": "./src/components/ui/contact-alert-dialog/index.tsx",
508
+ "import": "./dist/components/ui/contact-alert-dialog/index.mjs",
509
+ "require": "./dist/components/ui/contact-alert-dialog/index.js"
509
510
  },
510
511
  "./share-details-dialog": {
511
512
  "types": "./src/components/ui/share-details-dialog.tsx",
@@ -461,6 +461,8 @@ export {
461
461
  ContactAlertQueryBuilder,
462
462
  ContactAlertDialog,
463
463
  ALERT_QUERY_FIELDS,
464
+ AlertSharingType,
465
+ createAlertTree,
464
466
  } from "./ui/contact-alert-dialog";
465
467
  export type {
466
468
  AlertQueryCombinator,
@@ -468,11 +470,10 @@ export type {
468
470
  AlertQueryFieldType,
469
471
  ContactAlertSeverity,
470
472
  AlertQueryField,
471
- AlertQueryRule,
472
- AlertQueryGroup,
473
473
  ContactAlertQueryBuilderProps,
474
474
  ContactAlertDialogProps,
475
475
  } from "./ui/contact-alert-dialog";
476
+ export type { ImmutableTree, JsonGroup } from "@react-awesome-query-builder/ui";
476
477
 
477
478
  export {
478
479
  ShareDetailsDialog,
@@ -0,0 +1,556 @@
1
+ import * as React from "react";
2
+ import { ChevronDownIcon, PlusIcon, Trash2Icon } from "lucide-react";
3
+ import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
4
+ import {
5
+ Utils as QbUtils,
6
+ type Actions,
7
+ type ImmutableTree,
8
+ type JsonItem,
9
+ } from "@react-awesome-query-builder/ui";
10
+ import { Button } from "@/components/ui/button";
11
+ import {
12
+ InputGroup,
13
+ InputGroupAddon,
14
+ InputGroupInput,
15
+ InputGroupText,
16
+ } from "@/components/ui/input-group";
17
+ import { Checkbox } from "@/components/ui/checkbox";
18
+ import { Label } from "@/components/ui/label";
19
+ import {
20
+ Select,
21
+ SelectContent,
22
+ SelectItem,
23
+ SelectTrigger,
24
+ SelectValue,
25
+ } from "@/components/ui/select";
26
+ import { cn } from "@/lib/utils";
27
+ import type { AlertQueryField, AlertQueryOperator } from "./types";
28
+ import {
29
+ ALL_NUMERIC_OPERATORS,
30
+ BOOLEAN_OPERATORS,
31
+ OPERATOR_LABELS,
32
+ } from "./config";
33
+ import {
34
+ allOperatorLabels,
35
+ formatWithCommas,
36
+ longestOf,
37
+ parseCommas,
38
+ ruleSummary,
39
+ } from "./utils";
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // SelectAutoWidth
43
+ // ---------------------------------------------------------------------------
44
+
45
+ function SelectAutoWidth({
46
+ longestLabel,
47
+ children,
48
+ }: {
49
+ longestLabel: string;
50
+ children: React.ReactNode;
51
+ }) {
52
+ return (
53
+ <div className="relative inline-block shrink-0">
54
+ <span
55
+ aria-hidden
56
+ className="invisible block h-8 whitespace-nowrap pl-3 pr-10 text-body-medium"
57
+ >
58
+ {longestLabel}
59
+ </span>
60
+ <div className="absolute inset-0">{children}</div>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // ValueInput
67
+ // ---------------------------------------------------------------------------
68
+
69
+ function ValueInput({
70
+ value,
71
+ onChange,
72
+ unit,
73
+ placeholder = "0",
74
+ }: {
75
+ value: string;
76
+ onChange: (v: string) => void;
77
+ unit?: "dollar" | "percent";
78
+ placeholder?: string;
79
+ }) {
80
+ return (
81
+ <InputGroup className="w-36">
82
+ {unit === "dollar" && (
83
+ <InputGroupAddon align="inline-start">
84
+ <InputGroupText>$</InputGroupText>
85
+ </InputGroupAddon>
86
+ )}
87
+ <InputGroupInput
88
+ type="text"
89
+ inputMode="numeric"
90
+ value={formatWithCommas(value)}
91
+ onChange={(e) => onChange(parseCommas(e.target.value))}
92
+ placeholder={placeholder}
93
+ />
94
+ {unit === "percent" && (
95
+ <InputGroupAddon align="inline-end">
96
+ <InputGroupText>%</InputGroupText>
97
+ </InputGroupAddon>
98
+ )}
99
+ </InputGroup>
100
+ );
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // RuleEditorFields
105
+ // ---------------------------------------------------------------------------
106
+
107
+ function RuleEditorFields({
108
+ ruleProps,
109
+ path,
110
+ actions,
111
+ fields,
112
+ longestFieldLabel,
113
+ longestOperatorLabel,
114
+ }: {
115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
+ ruleProps: any;
117
+ path: string[];
118
+ actions: Actions;
119
+ fields: AlertQueryField[];
120
+ longestFieldLabel: string;
121
+ longestOperatorLabel: string;
122
+ }) {
123
+ const field: string = ruleProps?.field ?? fields[0]?.key ?? "";
124
+ const operator: string = ruleProps?.operator ?? "greater_or_equal";
125
+ const value0 = ruleProps?.value?.[0];
126
+ const value1 = ruleProps?.value?.[1];
127
+
128
+ const fieldDef = fields.find((f) => f.key === field) ?? fields[0];
129
+ const isBooleanField = fieldDef?.type === "boolean";
130
+ const availableOperators: AlertQueryOperator[] = isBooleanField
131
+ ? BOOLEAN_OPERATORS
132
+ : (fieldDef?.operators ?? ALL_NUMERIC_OPERATORS);
133
+
134
+ function handleFieldChange(key: string) {
135
+ actions.setField(path, key);
136
+ }
137
+
138
+ function handleOperatorChange(op: string) {
139
+ actions.setOperator(path, op);
140
+ }
141
+
142
+ function handleValueChange(idx: number, raw: string) {
143
+ const n = parseFloat(parseCommas(raw));
144
+ if (!isNaN(n)) {
145
+ actions.setValue(path, idx, n, "number");
146
+ } else if (!raw) {
147
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
+ actions.setValue(path, idx, null as any, "number");
149
+ }
150
+ }
151
+
152
+ function handleBoolChange(v: string) {
153
+ actions.setValue(path, 0, v === "true", "boolean");
154
+ }
155
+
156
+ const valStr = (v: unknown): string => {
157
+ if (v === null || v === undefined) return "";
158
+ return String(v);
159
+ };
160
+
161
+ return (
162
+ <div className="flex flex-wrap items-center gap-1.5">
163
+ {/* Field */}
164
+ <SelectAutoWidth longestLabel={longestFieldLabel}>
165
+ <Select
166
+ value={field}
167
+ onValueChange={(v) => handleFieldChange(v as string)}
168
+ >
169
+ <SelectTrigger size="sm" className="w-full">
170
+ <SelectValue>
171
+ {fields.find((f) => f.key === field)?.label ?? field}
172
+ </SelectValue>
173
+ </SelectTrigger>
174
+ <SelectContent>
175
+ {fields.map((f) => (
176
+ <SelectItem key={f.key} value={f.key}>
177
+ {f.label}
178
+ </SelectItem>
179
+ ))}
180
+ </SelectContent>
181
+ </Select>
182
+ </SelectAutoWidth>
183
+
184
+ {/* Operator */}
185
+ <SelectAutoWidth longestLabel={longestOperatorLabel}>
186
+ <Select
187
+ value={operator}
188
+ onValueChange={(v) => handleOperatorChange(v as string)}
189
+ >
190
+ <SelectTrigger size="sm" className="w-full">
191
+ <SelectValue>
192
+ {OPERATOR_LABELS[operator as AlertQueryOperator] ?? operator}
193
+ </SelectValue>
194
+ </SelectTrigger>
195
+ <SelectContent>
196
+ {availableOperators.map((op) => (
197
+ <SelectItem key={op} value={op}>
198
+ {OPERATOR_LABELS[op]}
199
+ </SelectItem>
200
+ ))}
201
+ </SelectContent>
202
+ </Select>
203
+ </SelectAutoWidth>
204
+
205
+ {/* Value */}
206
+ {isBooleanField ? (
207
+ <Select
208
+ value={String(value0 ?? true)}
209
+ onValueChange={(v) => handleBoolChange(v as string)}
210
+ >
211
+ <SelectTrigger size="sm" className="w-36">
212
+ <SelectValue>{(value0 ?? true) ? "Yes" : "No"}</SelectValue>
213
+ </SelectTrigger>
214
+ <SelectContent>
215
+ <SelectItem value="true">Yes</SelectItem>
216
+ <SelectItem value="false">No</SelectItem>
217
+ </SelectContent>
218
+ </Select>
219
+ ) : (
220
+ <>
221
+ <ValueInput
222
+ value={valStr(value0)}
223
+ onChange={(v) => handleValueChange(0, v)}
224
+ unit={fieldDef?.unit}
225
+ />
226
+ {operator === "between" && (
227
+ <>
228
+ <span className="text-xs text-muted-foreground">and</span>
229
+ <ValueInput
230
+ value={valStr(value1)}
231
+ onChange={(v) => handleValueChange(1, v)}
232
+ unit={fieldDef?.unit}
233
+ />
234
+ </>
235
+ )}
236
+ </>
237
+ )}
238
+ </div>
239
+ );
240
+ }
241
+
242
+ // ---------------------------------------------------------------------------
243
+ // RuleAccordionItem
244
+ // ---------------------------------------------------------------------------
245
+
246
+ function RuleAccordionItem({
247
+ ruleId,
248
+ ruleProps,
249
+ path,
250
+ actions,
251
+ fields,
252
+ longestFieldLabel,
253
+ longestOperatorLabel,
254
+ removable,
255
+ }: {
256
+ ruleId: string;
257
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
258
+ ruleProps: any;
259
+ path: string[];
260
+ actions: Actions;
261
+ fields: AlertQueryField[];
262
+ longestFieldLabel: string;
263
+ longestOperatorLabel: string;
264
+ removable: boolean;
265
+ }) {
266
+ const summary = ruleSummary(ruleProps, fields);
267
+
268
+ return (
269
+ <AccordionPrimitive.Item value={ruleId} className="border-b">
270
+ <AccordionPrimitive.Header className="flex items-center">
271
+ <AccordionPrimitive.Trigger
272
+ className={cn(
273
+ "flex flex-1 items-center gap-3 py-3 text-left text-sm outline-none",
274
+ "rounded-none hover:underline",
275
+ "focus-visible:ring-2 focus-visible:ring-foreground/30",
276
+ "[&[data-panel-open]>svg:last-child]:rotate-180",
277
+ )}
278
+ >
279
+ <span className="flex-1 text-sm text-foreground">{summary}</span>
280
+ <ChevronDownIcon className="size-4 shrink-0 text-muted-foreground transition-transform duration-200" />
281
+ </AccordionPrimitive.Trigger>
282
+ <Button
283
+ type="button"
284
+ variant="ghost"
285
+ size="sm"
286
+ className="shrink-0 px-2"
287
+ onClick={() => actions.removeRule(path)}
288
+ disabled={!removable}
289
+ aria-label="Remove rule"
290
+ >
291
+ <Trash2Icon className="size-4" />
292
+ </Button>
293
+ </AccordionPrimitive.Header>
294
+ <AccordionPrimitive.Panel className="overflow-hidden h-(--accordion-panel-height) transition-[height] duration-200 ease-out data-starting-style:h-0 data-ending-style:h-0">
295
+ <div className="pb-3">
296
+ <RuleEditorFields
297
+ ruleProps={ruleProps}
298
+ path={path}
299
+ actions={actions}
300
+ fields={fields}
301
+ longestFieldLabel={longestFieldLabel}
302
+ longestOperatorLabel={longestOperatorLabel}
303
+ />
304
+ </div>
305
+ </AccordionPrimitive.Panel>
306
+ </AccordionPrimitive.Item>
307
+ );
308
+ }
309
+
310
+ // ---------------------------------------------------------------------------
311
+ // SubGroupAccordionItem
312
+ // ---------------------------------------------------------------------------
313
+
314
+ function SubGroupAccordionItem({
315
+ groupId,
316
+ group,
317
+ path,
318
+ actions,
319
+ fields,
320
+ longestFieldLabel,
321
+ longestOperatorLabel,
322
+ }: {
323
+ groupId: string;
324
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
325
+ group: any;
326
+ path: string[];
327
+ actions: Actions;
328
+ fields: AlertQueryField[];
329
+ longestFieldLabel: string;
330
+ longestOperatorLabel: string;
331
+ }) {
332
+ const conjunction: string = group.properties?.conjunction ?? "AND";
333
+ const nestedChildren: JsonItem[] = group.children1 ?? [];
334
+ const count = nestedChildren.length;
335
+ const triggerLabel = `Group (${count} ${count === 1 ? "rule" : "rules"})`;
336
+
337
+ return (
338
+ <AccordionPrimitive.Item value={groupId} className="border-b">
339
+ <AccordionPrimitive.Header className="flex items-center">
340
+ <AccordionPrimitive.Trigger
341
+ className={cn(
342
+ "flex flex-1 items-center gap-3 py-3 text-left text-sm outline-none",
343
+ "rounded-none hover:underline",
344
+ "focus-visible:ring-2 focus-visible:ring-foreground/30",
345
+ "[&[data-panel-open]>svg:last-child]:rotate-180",
346
+ )}
347
+ >
348
+ <span className="flex-1 text-sm font-medium text-foreground">
349
+ {triggerLabel}
350
+ </span>
351
+ <ChevronDownIcon className="size-4 shrink-0 text-muted-foreground transition-transform duration-200" />
352
+ </AccordionPrimitive.Trigger>
353
+ <Button
354
+ type="button"
355
+ variant="ghost"
356
+ size="sm"
357
+ className="shrink-0 px-2"
358
+ onClick={() => actions.removeGroup(path)}
359
+ aria-label="Remove group"
360
+ >
361
+ <Trash2Icon className="size-4" />
362
+ </Button>
363
+ </AccordionPrimitive.Header>
364
+ <AccordionPrimitive.Panel className="overflow-hidden h-(--accordion-panel-height) transition-[height] duration-200 ease-out data-starting-style:h-0 data-ending-style:h-0">
365
+ <div className="pb-3 pl-4 border-l ml-2 flex flex-col gap-3">
366
+ {/* Nested combinator */}
367
+ <div className="flex items-center gap-2">
368
+ <Checkbox
369
+ id={`subgroup-${groupId}-combinator`}
370
+ checked={conjunction === "AND"}
371
+ onCheckedChange={(checked) =>
372
+ actions.setConjunction(path, checked ? "AND" : "OR")
373
+ }
374
+ />
375
+ <Label
376
+ htmlFor={`subgroup-${groupId}-combinator`}
377
+ className="cursor-pointer font-normal text-sm"
378
+ >
379
+ Match all conditions
380
+ </Label>
381
+ <span className="text-xs text-muted-foreground">
382
+ ({conjunction})
383
+ </span>
384
+ </div>
385
+
386
+ {/* Nested rules — flat rows */}
387
+ {nestedChildren.map((child) => {
388
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
389
+ const c = child as any;
390
+ const ruleId: string = c.id ?? "";
391
+ const rulePath = [...path, ruleId];
392
+ return (
393
+ <div key={ruleId} className="flex items-start gap-1.5">
394
+ <div className="flex-1">
395
+ <RuleEditorFields
396
+ ruleProps={c.properties}
397
+ path={rulePath}
398
+ actions={actions}
399
+ fields={fields}
400
+ longestFieldLabel={longestFieldLabel}
401
+ longestOperatorLabel={longestOperatorLabel}
402
+ />
403
+ </div>
404
+ <Button
405
+ type="button"
406
+ variant="ghost"
407
+ size="sm"
408
+ className="shrink-0 px-2"
409
+ onClick={() => actions.removeRule(rulePath)}
410
+ disabled={nestedChildren.length <= 1}
411
+ aria-label="Remove rule"
412
+ >
413
+ <Trash2Icon className="size-4" />
414
+ </Button>
415
+ </div>
416
+ );
417
+ })}
418
+
419
+ {/* Add rule inside group */}
420
+ <Button
421
+ type="button"
422
+ variant="ghost"
423
+ size="sm"
424
+ onClick={() => actions.addRule(path)}
425
+ className="w-fit"
426
+ >
427
+ <PlusIcon className="size-4" />
428
+ Add rule
429
+ </Button>
430
+ </div>
431
+ </AccordionPrimitive.Panel>
432
+ </AccordionPrimitive.Item>
433
+ );
434
+ }
435
+
436
+ // ---------------------------------------------------------------------------
437
+ // CustomBuilderUI
438
+ // ---------------------------------------------------------------------------
439
+
440
+ export interface CustomBuilderUIProps {
441
+ tree: ImmutableTree;
442
+ actions: Actions;
443
+ fields: AlertQueryField[];
444
+ defaultOpenItems: string[];
445
+ className?: string;
446
+ }
447
+
448
+ export function CustomBuilderUI({
449
+ tree,
450
+ actions,
451
+ fields,
452
+ defaultOpenItems,
453
+ className,
454
+ }: CustomBuilderUIProps) {
455
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
456
+ const treeJson = QbUtils.getTree(tree) as any;
457
+ const rootId: string = treeJson?.id ?? "root";
458
+ const conjunction: string = treeJson?.properties?.conjunction ?? "AND";
459
+ const children: JsonItem[] = treeJson?.children1 ?? [];
460
+
461
+ const longestFieldLabel = React.useMemo(
462
+ () => longestOf(fields.map((f) => f.label)),
463
+ [fields],
464
+ );
465
+ const longestOperatorLabel = React.useMemo(
466
+ () => longestOf(allOperatorLabels(fields)),
467
+ [fields],
468
+ );
469
+
470
+ return (
471
+ <div className={cn("flex flex-col gap-3", className)}>
472
+ {/* Root combinator */}
473
+ <div className="flex items-center gap-2">
474
+ <Checkbox
475
+ id="qb-root-combinator"
476
+ checked={conjunction === "AND"}
477
+ onCheckedChange={(checked) =>
478
+ actions.setConjunction([rootId], checked ? "AND" : "OR")
479
+ }
480
+ />
481
+ <Label
482
+ htmlFor="qb-root-combinator"
483
+ className="cursor-pointer font-normal text-sm"
484
+ >
485
+ Match all conditions
486
+ </Label>
487
+ <span className="text-xs text-muted-foreground">({conjunction})</span>
488
+ </div>
489
+
490
+ {/* Rules and groups */}
491
+ <AccordionPrimitive.Root
492
+ multiple
493
+ defaultValue={defaultOpenItems}
494
+ className="border-t"
495
+ >
496
+ {children.map((child) => {
497
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
498
+ const c = child as any;
499
+ const childId: string = c.id ?? "";
500
+ const childPath = [rootId, childId];
501
+
502
+ if (c.type === "group") {
503
+ return (
504
+ <SubGroupAccordionItem
505
+ key={childId}
506
+ groupId={childId}
507
+ group={c}
508
+ path={childPath}
509
+ actions={actions}
510
+ fields={fields}
511
+ longestFieldLabel={longestFieldLabel}
512
+ longestOperatorLabel={longestOperatorLabel}
513
+ />
514
+ );
515
+ }
516
+
517
+ return (
518
+ <RuleAccordionItem
519
+ key={childId}
520
+ ruleId={childId}
521
+ ruleProps={c.properties}
522
+ path={childPath}
523
+ actions={actions}
524
+ fields={fields}
525
+ longestFieldLabel={longestFieldLabel}
526
+ longestOperatorLabel={longestOperatorLabel}
527
+ removable={children.length > 1}
528
+ />
529
+ );
530
+ })}
531
+ </AccordionPrimitive.Root>
532
+
533
+ {/* Action buttons */}
534
+ <div className="flex gap-2">
535
+ <Button
536
+ type="button"
537
+ variant="outline"
538
+ size="sm"
539
+ onClick={() => actions.addRule([rootId])}
540
+ >
541
+ <PlusIcon className="size-4" />
542
+ Add rule
543
+ </Button>
544
+ <Button
545
+ type="button"
546
+ variant="outline"
547
+ size="sm"
548
+ onClick={() => actions.addGroup([rootId])}
549
+ >
550
+ <PlusIcon className="size-4" />
551
+ Add group
552
+ </Button>
553
+ </div>
554
+ </div>
555
+ );
556
+ }