betterstart-cli 0.0.12 → 0.0.14

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 (99) hide show
  1. package/dist/assets/adapters/next/plugins/blog/plugin.ts +1 -1
  2. package/dist/assets/adapters/next/plugins/blog/schemas/menus.json +155 -0
  3. package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +2 -7
  4. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +9 -3
  5. package/dist/assets/adapters/next/templates/init/components/layouts/sidebar-branding.tsx +1 -1
  6. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-grid.tsx +107 -0
  7. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +58 -45
  8. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/lifecycle-hooks-tab.tsx +1 -1
  9. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +1 -1
  10. package/dist/assets/adapters/next/templates/init/components/shared/entity-filters-bar.tsx +1 -1
  11. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-drawer.tsx +1 -1
  12. package/dist/assets/adapters/next/templates/init/components/shared/page-header.tsx +20 -17
  13. package/dist/assets/adapters/next/templates/init/components/shared/sort-indicator.tsx +7 -3
  14. package/dist/assets/adapters/next/templates/init/data/navigation.ts +4 -1
  15. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-slash-menu.ts +28 -2
  16. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor.ts +22 -1
  17. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-table-button.ts +85 -0
  18. package/dist/assets/adapters/next/templates/init/hooks/use-audit-log.ts +23 -0
  19. package/dist/assets/adapters/next/templates/init/lib/actions/audit/cleanup-audit-logs.ts +63 -0
  20. package/dist/assets/adapters/next/templates/init/lib/actions/audit/get-audit-logs.ts +122 -0
  21. package/dist/assets/adapters/next/templates/init/lib/actions/audit/index.ts +19 -0
  22. package/dist/assets/adapters/next/templates/init/lib/actions/audit/record-audit-event.ts +37 -0
  23. package/dist/assets/adapters/next/templates/init/lib/actions/audit/register.ts +13 -0
  24. package/dist/assets/adapters/next/templates/init/lib/actions/audit/request-metadata.ts +37 -0
  25. package/dist/assets/adapters/next/templates/init/lib/actions/audit/types.ts +77 -0
  26. package/dist/assets/adapters/next/templates/init/lib/actions/forms/upsert-form-settings.ts +3 -0
  27. package/dist/assets/adapters/next/templates/init/lib/actions/media/create-media.ts +19 -0
  28. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media-bulk.ts +23 -0
  29. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media.ts +21 -0
  30. package/dist/assets/adapters/next/templates/init/lib/actions/media/update-media.ts +23 -1
  31. package/dist/assets/adapters/next/templates/init/lib/actions/profile/update-email.ts +17 -0
  32. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-files.ts +3 -0
  33. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-media-from-url.ts +3 -0
  34. package/dist/assets/adapters/next/templates/init/lib/actions/users/create-user.ts +19 -1
  35. package/dist/assets/adapters/next/templates/init/lib/actions/users/delete-user.ts +23 -1
  36. package/dist/assets/adapters/next/templates/init/lib/actions/users/update-user-role.ts +23 -1
  37. package/dist/assets/adapters/next/templates/init/lib/db/core/schema.ts +49 -0
  38. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/index.ts +6 -1
  39. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/register.ts +3 -0
  40. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/registry.ts +36 -1
  41. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +117 -12
  42. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/types.ts +35 -1
  43. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-detail-drawer.tsx +104 -0
  44. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-detail-row.tsx +15 -0
  45. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-content.tsx +565 -0
  46. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-skeleton.tsx +7 -0
  47. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page.tsx +14 -0
  48. package/dist/assets/adapters/next/templates/init/pages/users/users-page-content.tsx +83 -2
  49. package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +126 -78
  50. package/dist/assets/adapters/next/templates/init/utils/audit/audit.ts +54 -0
  51. package/dist/assets/adapters/next/templates/init/utils/editor/tiptap.ts +7 -32
  52. package/dist/assets/adapters/next/templates/init/utils/form/list-field.ts +17 -0
  53. package/dist/assets/adapters/next/templates/init/utils/user/user.ts +70 -0
  54. package/dist/assets/shared-assets/react-admin/custom/content-editor/horizontal-rule-button.tsx +1 -1
  55. package/dist/assets/shared-assets/react-admin/custom/content-editor/index.tsx +6 -0
  56. package/dist/assets/shared-assets/react-admin/custom/content-editor/main-toolbar-content.tsx +1 -1
  57. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-button.tsx +1 -1
  58. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-editor-controls.tsx +27 -40
  59. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-popover-button.tsx +0 -1
  60. package/dist/assets/shared-assets/react-admin/custom/content-editor/mobile-toolbar-content.tsx +4 -4
  61. package/dist/assets/shared-assets/react-admin/custom/content-editor/mode-toggle-button.tsx +1 -1
  62. package/dist/assets/shared-assets/react-admin/custom/content-editor/selection-bubble-menu.tsx +45 -8
  63. package/dist/assets/shared-assets/react-admin/custom/content-editor/slash-command-menu.tsx +153 -27
  64. package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode-dropdown-button.tsx +1 -1
  65. package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode.tsx +9 -11
  66. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-bubble-menu.tsx +1 -1
  67. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-button.tsx +24 -67
  68. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/blockquote-button/blockquote-button.tsx +1 -1
  69. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/code-block-button/code-block-button.tsx +1 -1
  70. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/color-highlight-popover-button.tsx +1 -1
  71. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/color-highlight-popover-content.tsx +1 -1
  72. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +11 -2
  73. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-popover/source-color-highlight-popover.tsx +1 -1
  74. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu-item.tsx +1 -1
  75. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +9 -4
  76. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-button.tsx +1 -1
  77. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-control-popover.tsx +5 -12
  78. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-main.tsx +19 -45
  79. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-popover.tsx +2 -3
  80. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/list-dropdown-menu-item.tsx +1 -1
  81. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +9 -4
  82. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/mark-button/mark-button.tsx +1 -1
  83. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/undo-redo-button/undo-redo-button.tsx +1 -1
  84. package/dist/assets/shared-assets/react-admin/custom/date-range-picker.tsx +2 -2
  85. package/dist/assets/shared-assets/react-admin/custom/dynamic-list-field.tsx +20 -8
  86. package/dist/assets/shared-assets/react-admin/custom/media-gallery-field.tsx +1 -1
  87. package/dist/assets/shared-assets/react-admin/custom/nested-object-list-field.tsx +211 -0
  88. package/dist/assets/shared-assets/react-admin/schema.json +136 -23
  89. package/dist/assets/shared-assets/react-admin/ui/button.tsx +18 -18
  90. package/dist/assets/shared-assets/react-admin/ui/card.tsx +1 -1
  91. package/dist/assets/shared-assets/react-admin/ui/checkbox.tsx +1 -1
  92. package/dist/assets/shared-assets/react-admin/ui/combobox.tsx +27 -3
  93. package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +9 -5
  94. package/dist/assets/shared-assets/react-admin/ui/dropdown-menu.tsx +27 -12
  95. package/dist/assets/shared-assets/react-admin/ui/form.tsx +1 -1
  96. package/dist/assets/shared-assets/react-admin/ui/sidebar.tsx +4 -4
  97. package/dist/cli.js +5396 -2385
  98. package/dist/cli.js.map +1 -1
  99. package/package.json +10 -2
@@ -0,0 +1,211 @@
1
+ 'use client'
2
+
3
+ import {
4
+ Accordion,
5
+ AccordionContent,
6
+ AccordionItem,
7
+ AccordionTrigger
8
+ } from '@admin/components/ui/accordion'
9
+ import { Button } from '@admin/components/ui/button'
10
+ import { Card, CardContent, CardHeader } from '@admin/components/ui/card'
11
+ import { FormDescription, FormField, FormItem, FormLabel } from '@admin/components/ui/form'
12
+ import { cloneDefaultValue, createListItemKey } from '@admin/utils/form/list-field'
13
+ import { cn } from '@admin/utils/shared/cn'
14
+ import { Plus, X } from 'lucide-react'
15
+ import * as React from 'react'
16
+ import { useFormContext, useWatch } from 'react-hook-form'
17
+
18
+ interface NestedObjectListFieldProps {
19
+ name: string
20
+ label: string
21
+ singularLabel: string
22
+ defaultValue: Record<string, unknown>
23
+ children: (index: number) => React.ReactNode
24
+ className?: string
25
+ description?: string
26
+ disabled?: boolean
27
+ hideLabel?: boolean
28
+ maxItems?: number
29
+ renderTitle?: (index: number) => React.ReactNode
30
+ validatePaths?: string[]
31
+ }
32
+
33
+ export function NestedObjectListField({
34
+ name,
35
+ label,
36
+ singularLabel,
37
+ defaultValue,
38
+ children,
39
+ className,
40
+ description,
41
+ disabled,
42
+ hideLabel,
43
+ maxItems,
44
+ renderTitle,
45
+ validatePaths
46
+ }: NestedObjectListFieldProps) {
47
+ const { control, setValue, trigger } = useFormContext()
48
+ const watchedItems = useWatch({
49
+ control,
50
+ name
51
+ })
52
+ const items = Array.isArray(watchedItems) ? watchedItems : []
53
+ const [expanded, setExpanded] = React.useState('')
54
+ const [itemKeys, setItemKeys] = React.useState<string[]>([])
55
+ const canAddMore = !maxItems || items.length < maxItems
56
+
57
+ React.useEffect(() => {
58
+ setItemKeys((currentKeys) => {
59
+ if (currentKeys.length === items.length) return currentKeys
60
+ if (currentKeys.length > items.length) {
61
+ return currentKeys.slice(0, items.length)
62
+ }
63
+
64
+ const missingKeyCount = items.length - currentKeys.length
65
+ const nextKeys = Array.from({ length: missingKeyCount }, createListItemKey)
66
+ return [...currentKeys, ...nextKeys]
67
+ })
68
+ }, [items.length])
69
+
70
+ async function handleAdd() {
71
+ if (!canAddMore) return
72
+ if (expanded) {
73
+ const isExpandedItemValid = await validateExpandedItem()
74
+ if (!isExpandedItemValid) return
75
+ }
76
+
77
+ const nextValue = cloneDefaultValue(defaultValue) as Record<string, unknown>
78
+ const nextItems = [...items, nextValue]
79
+ setValue(name, nextItems, {
80
+ shouldDirty: true,
81
+ shouldValidate: false
82
+ })
83
+ setItemKeys((currentKeys) => [...currentKeys, createListItemKey()])
84
+ setExpanded(`item-${nextItems.length}`)
85
+ }
86
+
87
+ const handleRemove = (index: number) => {
88
+ if (disabled) return
89
+
90
+ const nextItems = items.filter((_, itemIndex) => itemIndex !== index)
91
+ setValue(name, nextItems, {
92
+ shouldDirty: true,
93
+ shouldValidate: false
94
+ })
95
+ const keepOtherKeys = (_: string, itemIndex: number) => itemIndex !== index
96
+ setItemKeys((currentKeys) => currentKeys.filter(keepOtherKeys))
97
+ setExpanded('')
98
+ }
99
+
100
+ async function validateExpandedItem() {
101
+ if (!expanded) return true
102
+
103
+ const expandedIndex = Number(expanded.replace('item-', '')) - 1
104
+ if (expandedIndex < 0) return true
105
+
106
+ const itemPath = `${name}.${expandedIndex}`
107
+ const paths =
108
+ validatePaths && validatePaths.length > 0
109
+ ? validatePaths.map((path) => `${itemPath}.${path}`)
110
+ : [itemPath]
111
+
112
+ return trigger(paths)
113
+ }
114
+
115
+ async function handleExpandedChange(nextExpanded: string) {
116
+ if (expanded && nextExpanded && nextExpanded !== expanded) {
117
+ const isExpandedItemValid = await validateExpandedItem()
118
+ if (!isExpandedItemValid) return
119
+ }
120
+
121
+ setExpanded(nextExpanded)
122
+ }
123
+
124
+ const getTitle = (index: number) => {
125
+ if (renderTitle) return renderTitle(index)
126
+ return `${singularLabel} ${index + 1}`
127
+ }
128
+
129
+ return (
130
+ <FormItem className={cn('space-y-4', className)}>
131
+ {items.length === 0 ? (
132
+ <Card>
133
+ <CardContent className="flex flex-col items-center justify-center">
134
+ <p className="text-muted-foreground">No {label} added yet.</p>
135
+ <Button
136
+ type="button"
137
+ variant="outline"
138
+ onClick={handleAdd}
139
+ disabled={disabled || !canAddMore}
140
+ >
141
+ <Plus className="size-3" />
142
+ Add {singularLabel}
143
+ </Button>
144
+ </CardContent>
145
+ </Card>
146
+ ) : (
147
+ <Card className="space-y-4 border-t border-border pt-4">
148
+ <CardHeader className="flex items-center justify-between pl-5">
149
+ <FormField
150
+ control={control}
151
+ name={name}
152
+ render={() => (
153
+ <FormItem className="flex flex-col gap-1">
154
+ <FormLabel className={cn({ 'sr-only': hideLabel })}>{label}</FormLabel>
155
+ {description ? <FormDescription>{description}</FormDescription> : null}
156
+ </FormItem>
157
+ )}
158
+ />
159
+ <Button
160
+ type="button"
161
+ variant="outline"
162
+ onClick={handleAdd}
163
+ disabled={disabled || !canAddMore}
164
+ >
165
+ <Plus className="size-3" />
166
+ Add {singularLabel}
167
+ </Button>
168
+ </CardHeader>
169
+
170
+ <CardContent className="p-0!">
171
+ <Accordion
172
+ type="single"
173
+ collapsible
174
+ value={expanded}
175
+ onValueChange={handleExpandedChange}
176
+ className="border-none"
177
+ >
178
+ {items.map((_, index) => (
179
+ <AccordionItem
180
+ key={itemKeys[index] ?? `${name}-${index}`}
181
+ value={`item-${index + 1}`}
182
+ className="bg-transparent!"
183
+ >
184
+ <div className="relative py-1">
185
+ <AccordionTrigger className="flex min-h-12 w-full items-center gap-2 py-2">
186
+ <span className="w-full pr-16">{getTitle(index)}</span>
187
+ </AccordionTrigger>
188
+ <Button
189
+ type="button"
190
+ variant="ghost"
191
+ size="icon-sm"
192
+ className="absolute right-10 top-1/2 shrink-0 -translate-y-1/2"
193
+ aria-label={`Remove ${singularLabel}`}
194
+ onClick={() => handleRemove(index)}
195
+ disabled={disabled}
196
+ >
197
+ <X />
198
+ </Button>
199
+ </div>
200
+ <AccordionContent className="space-y-5 border rounded-lg border-border p-5 bg-background mb-4">
201
+ {children(index)}
202
+ </AccordionContent>
203
+ </AccordionItem>
204
+ ))}
205
+ </Accordion>
206
+ </CardContent>
207
+ </Card>
208
+ )}
209
+ </FormItem>
210
+ )
211
+ }
@@ -57,9 +57,6 @@
57
57
  "filters": {
58
58
  "$ref": "#/$defs/filters"
59
59
  },
60
- "autoSlugify": {
61
- "$ref": "#/$defs/autoSlugify"
62
- },
63
60
  "navGroup": {
64
61
  "type": "object",
65
62
  "description": "Sidebar navigation group. Items sharing the same group label appear together.",
@@ -1401,26 +1398,6 @@
1401
1398
  }
1402
1399
  }
1403
1400
  },
1404
- "autoSlugify": {
1405
- "type": "object",
1406
- "description": "Auto-slugification configuration for generating URL-friendly slugs from fields",
1407
- "additionalProperties": false,
1408
- "properties": {
1409
- "enabled": {
1410
- "type": "boolean",
1411
- "description": "Enable automatic slug generation",
1412
- "default": true
1413
- },
1414
- "sourceField": {
1415
- "type": "string",
1416
- "description": "Field to use as source for slug generation (e.g., 'name', 'title')"
1417
- },
1418
- "targetField": {
1419
- "type": "string",
1420
- "description": "Field to store the generated slug (typically 'slug')"
1421
- }
1422
- }
1423
- },
1424
1401
  "slotLayout": {
1425
1402
  "type": "object",
1426
1403
  "description": "Main/sidebar layout groups for an entity edit form or tab panel.",
@@ -1464,6 +1441,111 @@
1464
1441
  "$ref": "#/$defs/fieldWithCreatableSelect"
1465
1442
  }
1466
1443
  },
1444
+ "fieldArrayWithTitle": {
1445
+ "type": "array",
1446
+ "contains": {
1447
+ "$ref": "#/$defs/fieldWithTitle"
1448
+ }
1449
+ },
1450
+ "fieldWithTitle": {
1451
+ "type": "object",
1452
+ "anyOf": [
1453
+ {
1454
+ "properties": {
1455
+ "name": {
1456
+ "const": "title"
1457
+ },
1458
+ "type": {
1459
+ "enum": ["string", "varchar", "text"]
1460
+ }
1461
+ },
1462
+ "required": ["name", "type"]
1463
+ },
1464
+ {
1465
+ "properties": {
1466
+ "type": {
1467
+ "enum": ["group", "section"]
1468
+ },
1469
+ "fields": {
1470
+ "$ref": "#/$defs/fieldArrayWithTitle"
1471
+ }
1472
+ },
1473
+ "required": ["type", "fields"]
1474
+ },
1475
+ {
1476
+ "properties": {
1477
+ "tabs": {
1478
+ "type": "array",
1479
+ "contains": {
1480
+ "$ref": "#/$defs/tabWithTitle"
1481
+ }
1482
+ }
1483
+ },
1484
+ "required": ["tabs"]
1485
+ }
1486
+ ]
1487
+ },
1488
+ "tabWithTitle": {
1489
+ "type": "object",
1490
+ "anyOf": [
1491
+ {
1492
+ "properties": {
1493
+ "fields": {
1494
+ "$ref": "#/$defs/fieldArrayWithTitle"
1495
+ }
1496
+ },
1497
+ "required": ["fields"]
1498
+ },
1499
+ {
1500
+ "properties": {
1501
+ "slot": {
1502
+ "$ref": "#/$defs/slotLayoutWithTitle"
1503
+ }
1504
+ },
1505
+ "required": ["slot"]
1506
+ }
1507
+ ]
1508
+ },
1509
+ "slotLayoutWithTitle": {
1510
+ "type": "object",
1511
+ "anyOf": [
1512
+ {
1513
+ "properties": {
1514
+ "main": {
1515
+ "properties": {
1516
+ "fields": {
1517
+ "$ref": "#/$defs/fieldArrayWithTitle"
1518
+ }
1519
+ },
1520
+ "required": ["fields"]
1521
+ }
1522
+ },
1523
+ "required": ["main"]
1524
+ },
1525
+ {
1526
+ "properties": {
1527
+ "sidebar": {
1528
+ "properties": {
1529
+ "fields": {
1530
+ "$ref": "#/$defs/fieldArrayWithTitle"
1531
+ }
1532
+ },
1533
+ "required": ["fields"]
1534
+ }
1535
+ },
1536
+ "required": ["sidebar"]
1537
+ }
1538
+ ]
1539
+ },
1540
+ "stepWithTitle": {
1541
+ "type": "object",
1542
+ "properties": {
1543
+ "fields": {
1544
+ "$ref": "#/$defs/fieldArrayWithTitle"
1545
+ }
1546
+ },
1547
+ "required": ["fields"]
1548
+ },
1467
1549
  "fieldWithCreatableSelect": {
1468
1550
  "type": "object",
1469
1551
  "anyOf": [
@@ -1634,6 +1716,37 @@
1634
1716
  }
1635
1717
  ],
1636
1718
  "allOf": [
1719
+ {
1720
+ "anyOf": [
1721
+ {
1722
+ "properties": {
1723
+ "fields": {
1724
+ "$ref": "#/$defs/fieldArrayWithTitle"
1725
+ }
1726
+ },
1727
+ "required": ["fields"]
1728
+ },
1729
+ {
1730
+ "properties": {
1731
+ "slot": {
1732
+ "$ref": "#/$defs/slotLayoutWithTitle"
1733
+ }
1734
+ },
1735
+ "required": ["slot"]
1736
+ },
1737
+ {
1738
+ "properties": {
1739
+ "steps": {
1740
+ "type": "array",
1741
+ "contains": {
1742
+ "$ref": "#/$defs/stepWithTitle"
1743
+ }
1744
+ }
1745
+ },
1746
+ "required": ["steps"]
1747
+ }
1748
+ ]
1749
+ },
1637
1750
  {
1638
1751
  "if": {
1639
1752
  "properties": {
@@ -1,10 +1,10 @@
1
1
  import { cn } from '@admin/utils/shared/cn'
2
2
  import { cva, type VariantProps } from 'class-variance-authority'
3
3
  import { Slot } from 'radix-ui'
4
- import type * as React from 'react'
4
+ import * as React from 'react'
5
5
 
6
6
  const buttonVariants = cva(
7
- "group/button inline-flex shrink-0 items-center justify-center rounded-4xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 active:not-aria-[haspopup]:scale-97 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:text-current [&_svg:not([class*='size-'])]:size-3.5 cursor-pointer [&_svg]:[stroke-width:1.5]",
7
+ "group/button inline-flex shrink-0 items-center justify-center rounded-4xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 active:not-aria-[haspopup]:scale-97 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:text-current [&_svg:not([class*='size-'])]:size-3.5 cursor-pointer [&_svg]:[stroke-width:1.5] min-w-0",
8
8
  {
9
9
  variants: {
10
10
  variant: {
@@ -22,11 +22,10 @@ const buttonVariants = cva(
22
22
  link: 'text-primary underline-offset-4 hover:underline'
23
23
  },
24
24
  size: {
25
- default:
26
- 'h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5',
27
- xs: "h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3",
28
- sm: 'h-8 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
29
- lg: 'h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3',
25
+ default: 'h-9 gap-1.5 px-3',
26
+ xs: "h-6 gap-1 px-2.5 text-xs [&_svg:not([class*='size-'])]:size-3",
27
+ sm: 'h-8 gap-1 px-3',
28
+ lg: 'h-10 gap-1.5 px-4',
30
29
  icon: 'size-9',
31
30
  'icon-xs': "size-6 [&_svg:not([class*='size-'])]:size-3",
32
31
  'icon-sm': 'size-8',
@@ -40,16 +39,16 @@ const buttonVariants = cva(
40
39
  }
41
40
  )
42
41
 
43
- function Button({
44
- className,
45
- variant = 'default',
46
- size = 'default',
47
- asChild = false,
48
- ...props
49
- }: React.ComponentProps<'button'> &
50
- VariantProps<typeof buttonVariants> & {
51
- asChild?: boolean
52
- }) {
42
+ const Button = React.forwardRef<
43
+ HTMLButtonElement,
44
+ React.ComponentProps<'button'> &
45
+ VariantProps<typeof buttonVariants> & {
46
+ asChild?: boolean
47
+ }
48
+ >(function Button(
49
+ { className, variant = 'default', size = 'default', asChild = false, ...props },
50
+ ref
51
+ ) {
53
52
  const Comp = asChild ? Slot.Root : 'button'
54
53
 
55
54
  return (
@@ -58,9 +57,10 @@ function Button({
58
57
  data-variant={variant}
59
58
  data-size={size}
60
59
  className={cn(buttonVariants({ variant, size, className }))}
60
+ ref={ref}
61
61
  {...props}
62
62
  />
63
63
  )
64
- }
64
+ })
65
65
 
66
66
  export { Button, buttonVariants }
@@ -83,7 +83,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
83
83
  <div
84
84
  data-slot="card-footer"
85
85
  className={cn(
86
- 'flex items-center border border-border bg-muted -mt-2 pt-3.5 pb-2 rounded-b-lg px-4',
86
+ 'flex items-center border border-border bg-muted -mt-2 pt-3.5 pb-2 rounded-b-xl px-4',
87
87
  className
88
88
  )}
89
89
  {...props}
@@ -10,7 +10,7 @@ function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxP
10
10
  <CheckboxPrimitive.Root
11
11
  data-slot="checkbox"
12
12
  className={cn(
13
- 'peer relative flex size-4.5 shrink-0 items-center justify-center rounded-[6px] border border-border transition-shadow outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary',
13
+ 'peer relative flex size-4.5 shrink-0 items-center justify-center rounded-[6px] border border-border transition-shadow outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary bg-input',
14
14
  className
15
15
  )}
16
16
  {...props}
@@ -48,16 +48,22 @@ function ComboboxInput({
48
48
  className,
49
49
  children,
50
50
  disabled = false,
51
+ ref,
51
52
  showTrigger = true,
52
53
  showClear = false,
53
54
  ...props
54
55
  }: ComboboxPrimitive.Input.Props & {
56
+ ref?: React.Ref<HTMLInputElement>
55
57
  showTrigger?: boolean
56
58
  showClear?: boolean
57
59
  }) {
58
60
  return (
59
61
  <InputGroup className={cn('w-auto', className)}>
60
- <ComboboxPrimitive.Input render={<InputGroupInput disabled={disabled} />} {...props} />
62
+ <ComboboxPrimitive.Input
63
+ ref={ref}
64
+ render={<InputGroupInput disabled={disabled} />}
65
+ {...props}
66
+ />
61
67
  <InputGroupAddon align="inline-end">
62
68
  {showTrigger && (
63
69
  <InputGroupButton
@@ -85,11 +91,19 @@ function ComboboxContent({
85
91
  align = 'start',
86
92
  alignOffset = 0,
87
93
  anchor,
94
+ collisionPadding,
95
+ positionMethod,
88
96
  ...props
89
97
  }: ComboboxPrimitive.Popup.Props &
90
98
  Pick<
91
99
  ComboboxPrimitive.Positioner.Props,
92
- 'side' | 'align' | 'sideOffset' | 'alignOffset' | 'anchor'
100
+ | 'side'
101
+ | 'align'
102
+ | 'sideOffset'
103
+ | 'alignOffset'
104
+ | 'anchor'
105
+ | 'collisionPadding'
106
+ | 'positionMethod'
93
107
  >) {
94
108
  return (
95
109
  <ComboboxPrimitive.Portal>
@@ -99,6 +113,8 @@ function ComboboxContent({
99
113
  align={align}
100
114
  alignOffset={alignOffset}
101
115
  anchor={anchor}
116
+ collisionPadding={collisionPadding}
117
+ positionMethod={positionMethod}
102
118
  className="isolate z-50"
103
119
  >
104
120
  <ComboboxPrimitive.Popup
@@ -128,9 +144,17 @@ function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
128
144
  )
129
145
  }
130
146
 
131
- function ComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item.Props) {
147
+ function ComboboxItem({
148
+ className,
149
+ children,
150
+ ref,
151
+ ...props
152
+ }: ComboboxPrimitive.Item.Props & {
153
+ ref?: React.Ref<HTMLDivElement>
154
+ }) {
132
155
  return (
133
156
  <ComboboxPrimitive.Item
157
+ ref={ref}
134
158
  data-slot="combobox-item"
135
159
  className={cn(
136
160
  "relative flex w-full cursor-default items-center gap-2.5 rounded-lg py-2 pr-8 pl-3 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
@@ -4,8 +4,11 @@ import { cn } from '@admin/utils/shared/cn'
4
4
  import type * as React from 'react'
5
5
  import { Drawer as DrawerPrimitive } from 'vaul'
6
6
 
7
- function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
8
- return <DrawerPrimitive.Root data-slot="drawer" {...props} />
7
+ function Drawer({
8
+ handleOnly = true,
9
+ ...props
10
+ }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
11
+ return <DrawerPrimitive.Root data-slot="drawer" handleOnly={handleOnly} {...props} />
9
12
  }
10
13
 
11
14
  function DrawerTrigger({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
@@ -39,6 +42,7 @@ function DrawerOverlay({
39
42
  function DrawerContent({
40
43
  className,
41
44
  children,
45
+ style,
42
46
  ...props
43
47
  }: React.ComponentProps<typeof DrawerPrimitive.Content>) {
44
48
  return (
@@ -47,12 +51,12 @@ function DrawerContent({
47
51
  <DrawerPrimitive.Content
48
52
  data-slot="drawer-content"
49
53
  className={cn(
50
- 'group/drawer-content fixed z-50 flex h-auto flex-col bg-transparent p-4 text-sm text-popover-foreground before:absolute before:inset-2 before:-z-10 before:rounded-4xl before:border before:border-border before:bg-popover data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-md',
54
+ 'group/drawer-content fixed z-50 flex h-auto flex-col bg-transparent p-2 text-sm text-popover-foreground before:absolute before:inset-2 before:-z-10 before:rounded-4xl before:border before:border-border before:bg-popover data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-md space-y-4',
51
55
  className
52
56
  )}
57
+ style={{ touchAction: 'auto', userSelect: 'text', ...style }}
53
58
  {...props}
54
59
  >
55
- <div className="mx-auto mt-4 hidden h-1.5 w-25 shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
56
60
  {children}
57
61
  </DrawerPrimitive.Content>
58
62
  </DrawerPortal>
@@ -64,7 +68,7 @@ function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {
64
68
  <div
65
69
  data-slot="drawer-header"
66
70
  className={cn(
67
- 'flex flex-col p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:text-left',
71
+ 'flex flex-col px-6 pt-6 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:text-left',
68
72
  className
69
73
  )}
70
74
  {...props}
@@ -28,24 +28,39 @@ function DropdownMenuTrigger({
28
28
  )
29
29
  }
30
30
 
31
+ type DropdownMenuContentProps = React.ComponentProps<typeof DropdownMenuPrimitive.Content> & {
32
+ portalContainer?: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>['container']
33
+ portalled?: boolean
34
+ }
35
+
31
36
  function DropdownMenuContent({
32
37
  className,
33
38
  align = 'start',
39
+ portalContainer,
40
+ portalled = true,
34
41
  sideOffset = 4,
35
42
  ...props
36
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
43
+ }: DropdownMenuContentProps) {
44
+ const content = (
45
+ <DropdownMenuPrimitive.Content
46
+ data-slot="dropdown-menu-content"
47
+ sideOffset={sideOffset}
48
+ align={align}
49
+ className={cn(
50
+ 'z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-35 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 text-popover-foreground shadow-2xl/10 ring-1 ring-foreground/5 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden dark:ring-foreground/10 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10!',
51
+ className
52
+ )}
53
+ {...props}
54
+ />
55
+ )
56
+
57
+ if (!portalled) {
58
+ return content
59
+ }
60
+
37
61
  return (
38
- <DropdownMenuPrimitive.Portal>
39
- <DropdownMenuPrimitive.Content
40
- data-slot="dropdown-menu-content"
41
- sideOffset={sideOffset}
42
- align={align}
43
- className={cn(
44
- 'z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-35 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg p-1 text-popover-foreground shadow-2xl/10 ring-1 ring-foreground/5 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden dark:ring-foreground/10 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10!',
45
- className
46
- )}
47
- {...props}
48
- />
62
+ <DropdownMenuPrimitive.Portal container={portalContainer}>
63
+ {content}
49
64
  </DropdownMenuPrimitive.Portal>
50
65
  )
51
66
  }
@@ -129,7 +129,7 @@ const FormDescription = React.forwardRef<
129
129
  <p
130
130
  ref={ref}
131
131
  id={formDescriptionId}
132
- className={cn('text-[0.8rem] text-muted-foreground/70', className)}
132
+ className={cn('text-[0.8rem] text-muted-foreground', className)}
133
133
  {...props}
134
134
  />
135
135
  )