@dyrected/admin 2.4.0 → 2.4.1
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/dist/App.d.ts +1 -0
- package/dist/admin.css +2 -0
- package/dist/components/auth/auth-gate.d.ts +13 -0
- package/dist/components/error-boundary.d.ts +16 -0
- package/dist/components/forms/field-renderer.d.ts +22 -0
- package/dist/components/forms/fields/block-builder.d.ts +9 -0
- package/dist/components/forms/fields/date-picker.d.ts +8 -0
- package/dist/components/forms/fields/json-editor.d.ts +8 -0
- package/dist/components/forms/fields/media-picker.d.ts +12 -0
- package/dist/components/forms/fields/multi-select.d.ts +19 -0
- package/dist/components/forms/fields/radio-field.d.ts +8 -0
- package/dist/components/forms/fields/relationship-picker.d.ts +10 -0
- package/dist/components/forms/fields/rich-text-editor.d.ts +9 -0
- package/dist/components/forms/fields/select-field.d.ts +8 -0
- package/dist/components/forms/fields/switch-field.d.ts +6 -0
- package/dist/components/forms/fields/text-area-field.d.ts +8 -0
- package/dist/components/forms/fields/text-field.d.ts +8 -0
- package/dist/components/forms/form-engine.d.ts +14 -0
- package/dist/components/forms/form-field-renderer.d.ts +20 -0
- package/dist/components/forms/utils.d.ts +11 -0
- package/dist/components/layout/admin-shell.d.ts +5 -0
- package/dist/components/layout/branding-provider.d.ts +4 -0
- package/dist/components/live-preview/LivePreviewPane.d.ts +7 -0
- package/dist/components/media/focal-point-picker.d.ts +12 -0
- package/dist/components/media/media-card.d.ts +8 -0
- package/dist/components/media/media-grid.d.ts +8 -0
- package/dist/components/media/media-library-dialog.d.ts +11 -0
- package/dist/components/ui/aspect-ratio.d.ts +3 -0
- package/dist/components/ui/badge.d.ts +9 -0
- package/dist/components/ui/button.d.ts +11 -0
- package/dist/components/ui/calendar.d.ts +8 -0
- package/dist/components/ui/card.d.ts +8 -0
- package/dist/components/ui/checkbox.d.ts +4 -0
- package/dist/components/ui/command.d.ts +80 -0
- package/dist/components/ui/data-table.d.ts +14 -0
- package/dist/components/ui/dialog.d.ts +19 -0
- package/dist/components/ui/dropdown-menu.d.ts +27 -0
- package/dist/components/ui/form.d.ts +23 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/label.d.ts +5 -0
- package/dist/components/ui/page-header.d.ts +10 -0
- package/dist/components/ui/pagination.d.ts +11 -0
- package/dist/components/ui/popover.d.ts +6 -0
- package/dist/components/ui/progress.d.ts +4 -0
- package/dist/components/ui/radio-group.d.ts +5 -0
- package/dist/components/ui/render-cell.d.ts +8 -0
- package/dist/components/ui/scroll-area.d.ts +5 -0
- package/dist/components/ui/select.d.ts +13 -0
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/sheet.d.ts +25 -0
- package/dist/components/ui/sidebar.d.ts +65 -0
- package/dist/components/ui/skeleton.d.ts +2 -0
- package/dist/components/ui/sonner.d.ts +4 -0
- package/dist/components/ui/switch.d.ts +4 -0
- package/dist/components/ui/table.d.ts +10 -0
- package/dist/components/ui/tabs.d.ts +7 -0
- package/dist/components/ui/textarea.d.ts +3 -0
- package/dist/components/ui/toggle.d.ts +12 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/hooks/use-mobile.d.ts +1 -0
- package/dist/hooks/use-preferences.d.ts +6 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.mjs +69091 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/main.d.ts +0 -0
- package/dist/pages/auth/first-user-page.d.ts +4 -0
- package/dist/pages/auth/login-page.d.ts +4 -0
- package/dist/pages/collections/edit-page.d.ts +1 -0
- package/dist/pages/collections/list-page.d.ts +5 -0
- package/dist/pages/dashboard/dashboard.d.ts +1 -0
- package/dist/pages/globals/editor-page.d.ts +1 -0
- package/dist/pages/media/media-page.d.ts +4 -0
- package/dist/pages/setup/setup-prompt.d.ts +6 -0
- package/dist/providers/dyrected-provider.d.ts +29 -0
- package/dist/providers/query-provider.d.ts +3 -0
- package/package.json +6 -3
- package/CHANGELOG.md +0 -153
- package/components.json +0 -17
- package/eslint.config.js +0 -22
- package/index.html +0 -13
- package/postcss.config.js +0 -6
- package/scripts/prefix-tailwind-precision.py +0 -98
- package/scripts/prefix-tailwind.py +0 -67
- package/src/App.css +0 -184
- package/src/App.tsx +0 -25
- package/src/assets/dyrected.svg +0 -155
- package/src/assets/hero.png +0 -0
- package/src/assets/react.svg +0 -1
- package/src/assets/vite.svg +0 -1
- package/src/components/auth/auth-gate.tsx +0 -64
- package/src/components/error-boundary.tsx +0 -45
- package/src/components/forms/field-renderer.tsx +0 -111
- package/src/components/forms/fields/block-builder.tsx +0 -213
- package/src/components/forms/fields/date-picker.tsx +0 -60
- package/src/components/forms/fields/json-editor.tsx +0 -62
- package/src/components/forms/fields/media-picker.tsx +0 -286
- package/src/components/forms/fields/multi-select.tsx +0 -145
- package/src/components/forms/fields/radio-field.tsx +0 -51
- package/src/components/forms/fields/relationship-picker.tsx +0 -143
- package/src/components/forms/fields/rich-text-editor.tsx +0 -224
- package/src/components/forms/fields/select-field.tsx +0 -35
- package/src/components/forms/fields/switch-field.tsx +0 -16
- package/src/components/forms/fields/text-area-field.tsx +0 -15
- package/src/components/forms/fields/text-field.tsx +0 -24
- package/src/components/forms/form-engine.tsx +0 -87
- package/src/components/forms/form-field-renderer.tsx +0 -269
- package/src/components/forms/utils.ts +0 -97
- package/src/components/layout/admin-shell.tsx +0 -479
- package/src/components/layout/branding-provider.tsx +0 -112
- package/src/components/live-preview/LivePreviewPane.tsx +0 -128
- package/src/components/media/focal-point-picker.tsx +0 -66
- package/src/components/media/media-card.tsx +0 -44
- package/src/components/media/media-grid.tsx +0 -32
- package/src/components/media/media-library-dialog.tsx +0 -465
- package/src/components/ui/aspect-ratio.tsx +0 -7
- package/src/components/ui/badge.tsx +0 -36
- package/src/components/ui/button.tsx +0 -56
- package/src/components/ui/calendar.tsx +0 -214
- package/src/components/ui/card.tsx +0 -79
- package/src/components/ui/checkbox.tsx +0 -28
- package/src/components/ui/command.tsx +0 -151
- package/src/components/ui/data-table.tsx +0 -219
- package/src/components/ui/dialog.tsx +0 -122
- package/src/components/ui/dropdown-menu.tsx +0 -200
- package/src/components/ui/form.tsx +0 -178
- package/src/components/ui/input.tsx +0 -24
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/page-header.tsx +0 -30
- package/src/components/ui/pagination.tsx +0 -57
- package/src/components/ui/popover.tsx +0 -29
- package/src/components/ui/progress.tsx +0 -26
- package/src/components/ui/radio-group.tsx +0 -42
- package/src/components/ui/render-cell.tsx +0 -110
- package/src/components/ui/scroll-area.tsx +0 -46
- package/src/components/ui/select.tsx +0 -160
- package/src/components/ui/separator.tsx +0 -29
- package/src/components/ui/sheet.tsx +0 -140
- package/src/components/ui/sidebar.tsx +0 -771
- package/src/components/ui/skeleton.tsx +0 -15
- package/src/components/ui/sonner.tsx +0 -27
- package/src/components/ui/switch.tsx +0 -27
- package/src/components/ui/table.tsx +0 -117
- package/src/components/ui/tabs.tsx +0 -53
- package/src/components/ui/textarea.tsx +0 -22
- package/src/components/ui/toggle.tsx +0 -43
- package/src/components/ui/tooltip.tsx +0 -28
- package/src/hooks/use-mobile.tsx +0 -19
- package/src/hooks/use-preferences.ts +0 -56
- package/src/index.css +0 -111
- package/src/index.tsx +0 -198
- package/src/lib/utils.ts +0 -36
- package/src/main.tsx +0 -10
- package/src/pages/auth/first-user-page.tsx +0 -115
- package/src/pages/auth/login-page.tsx +0 -91
- package/src/pages/collections/edit-page.tsx +0 -280
- package/src/pages/collections/list-page.tsx +0 -343
- package/src/pages/dashboard/dashboard.tsx +0 -150
- package/src/pages/globals/editor-page.tsx +0 -122
- package/src/pages/media/media-page.tsx +0 -564
- package/src/pages/setup/setup-prompt.tsx +0 -181
- package/src/providers/dyrected-provider.tsx +0 -122
- package/src/providers/query-provider.tsx +0 -19
- package/src/types/jexl.d.ts +0 -11
- package/tailwind.config.ts +0 -103
- package/tsconfig.app.json +0 -28
- package/tsconfig.json +0 -12
- package/tsconfig.node.json +0 -25
- package/vite.config.ts +0 -39
- /package/{public → dist}/favicon.svg +0 -0
- /package/{public → dist}/icons.svg +0 -0
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { Check, ChevronsUpDown, X } from "lucide-react"
|
|
3
|
-
|
|
4
|
-
import { cn } from "../../../lib/utils"
|
|
5
|
-
import { Button } from "../../ui/button"
|
|
6
|
-
import {
|
|
7
|
-
Command,
|
|
8
|
-
CommandEmpty,
|
|
9
|
-
CommandGroup,
|
|
10
|
-
CommandInput,
|
|
11
|
-
CommandItem,
|
|
12
|
-
CommandList,
|
|
13
|
-
} from "../../ui/command"
|
|
14
|
-
import {
|
|
15
|
-
Popover,
|
|
16
|
-
PopoverContent,
|
|
17
|
-
PopoverTrigger,
|
|
18
|
-
} from "../../ui/popover"
|
|
19
|
-
import { Badge } from "../../ui/badge"
|
|
20
|
-
|
|
21
|
-
interface Option {
|
|
22
|
-
label: string
|
|
23
|
-
value: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface MultiSelectProps {
|
|
27
|
-
options: Option[]
|
|
28
|
-
value?: string[]
|
|
29
|
-
onChange: (value: string[]) => void
|
|
30
|
-
label?: string
|
|
31
|
-
placeholder?: string
|
|
32
|
-
disabled?: boolean
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* MultiSelect Field component
|
|
37
|
-
*
|
|
38
|
-
* Provides a tag-based multi-selection UI using a searchable dropdown.
|
|
39
|
-
*/
|
|
40
|
-
export function MultiSelect({
|
|
41
|
-
options,
|
|
42
|
-
value = [],
|
|
43
|
-
onChange,
|
|
44
|
-
label,
|
|
45
|
-
placeholder = "Select options...",
|
|
46
|
-
disabled,
|
|
47
|
-
}: MultiSelectProps) {
|
|
48
|
-
const [open, setOpen] = React.useState(false)
|
|
49
|
-
|
|
50
|
-
const handleSelect = (currentValue: string) => {
|
|
51
|
-
const isSelected = value.includes(currentValue)
|
|
52
|
-
if (isSelected) {
|
|
53
|
-
onChange(value.filter((val) => val !== currentValue))
|
|
54
|
-
} else {
|
|
55
|
-
onChange([...value, currentValue])
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const handleRemove = (valueToRemove: string) => {
|
|
60
|
-
onChange(value.filter((val) => val !== valueToRemove))
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div className="dy-flex dy-flex-col dy-gap-2">
|
|
65
|
-
{label && <label className="dy-text-sm dy-font-medium dy-leading-none">{label}</label>}
|
|
66
|
-
<Popover open={disabled ? false : open} onOpenChange={setOpen}>
|
|
67
|
-
<PopoverTrigger asChild>
|
|
68
|
-
<Button
|
|
69
|
-
variant="outline"
|
|
70
|
-
role="combobox"
|
|
71
|
-
aria-expanded={open}
|
|
72
|
-
disabled={disabled}
|
|
73
|
-
className="dy-w-full dy-justify-between dy-h-auto dy-min-h-10 dy-font-normal"
|
|
74
|
-
>
|
|
75
|
-
<div className="dy-flex dy-flex-wrap dy-gap-1 dy-items-center">
|
|
76
|
-
{value.length === 0 && (
|
|
77
|
-
<span className="dy-text-muted-foreground">{placeholder}</span>
|
|
78
|
-
)}
|
|
79
|
-
{value.map((val) => {
|
|
80
|
-
const option = options.find((opt) => opt.value === val)
|
|
81
|
-
return (
|
|
82
|
-
<Badge
|
|
83
|
-
key={val}
|
|
84
|
-
variant="secondary"
|
|
85
|
-
className="dy-mr-1 dy-mb-1 dy-items-center dy-gap-1"
|
|
86
|
-
>
|
|
87
|
-
{option?.label || val}
|
|
88
|
-
{!disabled && (
|
|
89
|
-
<div
|
|
90
|
-
role="button"
|
|
91
|
-
tabIndex={0}
|
|
92
|
-
className="dy-ring-offset-background dy-rounded-full dy-outline-none focus:dy-ring-2 focus:dy-ring-ring focus:dy-ring-offset-2"
|
|
93
|
-
onKeyDown={(e) => {
|
|
94
|
-
if (e.key === "Enter") {
|
|
95
|
-
handleRemove(val)
|
|
96
|
-
}
|
|
97
|
-
}}
|
|
98
|
-
onMouseDown={(e) => {
|
|
99
|
-
e.preventDefault()
|
|
100
|
-
e.stopPropagation()
|
|
101
|
-
}}
|
|
102
|
-
onClick={() => handleRemove(val)}
|
|
103
|
-
>
|
|
104
|
-
<X className="dy-h-3 dy-w-3 dy-text-muted-foreground hover:dy-text-foreground" />
|
|
105
|
-
</div>
|
|
106
|
-
)}
|
|
107
|
-
</Badge>
|
|
108
|
-
)
|
|
109
|
-
})}
|
|
110
|
-
</div>
|
|
111
|
-
<ChevronsUpDown className="dy-ml-2 dy-h-4 dy-w-4 dy-shrink-0 dy-opacity-50" />
|
|
112
|
-
</Button>
|
|
113
|
-
</PopoverTrigger>
|
|
114
|
-
<PopoverContent className="dy-w-[400px] dy-p-0" align="start">
|
|
115
|
-
<Command>
|
|
116
|
-
<CommandInput placeholder="Search options..." />
|
|
117
|
-
<CommandList>
|
|
118
|
-
<CommandEmpty>No option found.</CommandEmpty>
|
|
119
|
-
<CommandGroup>
|
|
120
|
-
{options.map((option) => {
|
|
121
|
-
const isSelected = value.includes(option.value)
|
|
122
|
-
return (
|
|
123
|
-
<CommandItem
|
|
124
|
-
key={option.value}
|
|
125
|
-
value={option.label as any}
|
|
126
|
-
onSelect={() => handleSelect(option.value)}
|
|
127
|
-
>
|
|
128
|
-
<Check
|
|
129
|
-
className={cn(
|
|
130
|
-
"dy-mr-2 dy-h-4 dy-w-4",
|
|
131
|
-
isSelected ? "dy-opacity-100" : "dy-opacity-0"
|
|
132
|
-
)}
|
|
133
|
-
/>
|
|
134
|
-
{option.label}
|
|
135
|
-
</CommandItem>
|
|
136
|
-
)
|
|
137
|
-
})}
|
|
138
|
-
</CommandGroup>
|
|
139
|
-
</CommandList>
|
|
140
|
-
</Command>
|
|
141
|
-
</PopoverContent>
|
|
142
|
-
</Popover>
|
|
143
|
-
</div>
|
|
144
|
-
)
|
|
145
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { RadioGroup, RadioGroupItem } from "../../ui/radio-group"
|
|
2
|
-
import { Label } from "../../ui/label"
|
|
3
|
-
import { cn } from "../../../lib/utils"
|
|
4
|
-
import { normalizeOptions } from "../utils"
|
|
5
|
-
import type { Field as FieldSchema } from "@dyrected/sdk"
|
|
6
|
-
|
|
7
|
-
interface RadioFieldProps {
|
|
8
|
-
schema: FieldSchema
|
|
9
|
-
field: any
|
|
10
|
-
disabled?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function RadioField({ schema, field, disabled }: RadioFieldProps) {
|
|
14
|
-
const options = normalizeOptions(schema.options)
|
|
15
|
-
const isHorizontal = schema.admin?.direction === "horizontal"
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<RadioGroup
|
|
19
|
-
onValueChange={field.onChange}
|
|
20
|
-
defaultValue={field.value}
|
|
21
|
-
disabled={disabled}
|
|
22
|
-
className={cn(
|
|
23
|
-
"dy-gap-4",
|
|
24
|
-
isHorizontal ? "dy-flex dy-flex-wrap dy-items-center" : "dy-flex dy-flex-col"
|
|
25
|
-
)}
|
|
26
|
-
>
|
|
27
|
-
{options.map((opt) => (
|
|
28
|
-
<div key={opt.value} className={cn(
|
|
29
|
-
"dy-relative dy-flex dy-items-center",
|
|
30
|
-
isHorizontal ? "dy-min-w-[120px]" : "dy-w-full"
|
|
31
|
-
)}>
|
|
32
|
-
<RadioGroupItem
|
|
33
|
-
value={opt.value}
|
|
34
|
-
id={`${field.name}-${opt.value}`}
|
|
35
|
-
className="dy-peer dy-absolute dy-left-4 dy-z-10"
|
|
36
|
-
/>
|
|
37
|
-
<Label
|
|
38
|
-
htmlFor={`${field.name}-${opt.value}`}
|
|
39
|
-
className={cn(
|
|
40
|
-
"dy-flex dy-flex-1 dy-items-center dy-pl-12 dy-pr-4 dy-py-3 dy-rounded-xl dy-border dy-border-border/40 dy-bg-white/50 dy-cursor-pointer dy-transition-all hover:dy-bg-white/80 hover:dy-shadow-sm",
|
|
41
|
-
"dy-peer-data-[state=checked]:dy-border-primary dy-peer-data-[state=checked]:dy-bg-primary/5 dy-peer-data-[state=checked]:dy-shadow-md dy-peer-data-[state=checked]:dy-ring-1 dy-peer-data-[state=checked]:dy-ring-primary/20",
|
|
42
|
-
"dy-text-sm dy-font-medium dy-text-foreground/70 dy-peer-data-[state=checked]:dy-text-primary"
|
|
43
|
-
)}
|
|
44
|
-
>
|
|
45
|
-
{opt.label}
|
|
46
|
-
</Label>
|
|
47
|
-
</div>
|
|
48
|
-
))}
|
|
49
|
-
</RadioGroup>
|
|
50
|
-
)
|
|
51
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { useQuery } from "@tanstack/react-query"
|
|
3
|
-
import { useDyrected } from "../../../providers/dyrected-provider"
|
|
4
|
-
import { Button } from "../../ui/button"
|
|
5
|
-
import { Badge } from "../../ui/badge"
|
|
6
|
-
import {
|
|
7
|
-
Command,
|
|
8
|
-
CommandEmpty,
|
|
9
|
-
CommandGroup,
|
|
10
|
-
CommandInput,
|
|
11
|
-
CommandItem,
|
|
12
|
-
CommandList,
|
|
13
|
-
} from "../../ui/command"
|
|
14
|
-
import {
|
|
15
|
-
Popover,
|
|
16
|
-
PopoverContent,
|
|
17
|
-
PopoverTrigger,
|
|
18
|
-
} from "../../ui/popover"
|
|
19
|
-
import { Check, ChevronsUpDown } from "lucide-react"
|
|
20
|
-
import { cn, getMediaUrl } from "../../../lib/utils"
|
|
21
|
-
|
|
22
|
-
interface RelationshipPickerProps {
|
|
23
|
-
value?: string | string[]
|
|
24
|
-
onChange: (value: string | string[]) => void
|
|
25
|
-
label?: string
|
|
26
|
-
relationTo: string // The collection slug this field relates to
|
|
27
|
-
multiple?: boolean
|
|
28
|
-
disabled?: boolean
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function RelationshipPicker({ value, onChange, label, relationTo, multiple, disabled }: RelationshipPickerProps) {
|
|
32
|
-
const { client, schemas } = useDyrected()
|
|
33
|
-
const [open, setOpen] = React.useState(false)
|
|
34
|
-
const [search, setSearch] = React.useState("")
|
|
35
|
-
|
|
36
|
-
const relatedCollection = schemas?.collections.find((c: any) => c.slug === relationTo)
|
|
37
|
-
if (!relationTo) console.warn("[RelationshipPicker] No relationTo/collection defined for field:", label)
|
|
38
|
-
const isUpload = !!relatedCollection?.upload
|
|
39
|
-
const displayField = relatedCollection?.admin?.useAsTitle || "title"
|
|
40
|
-
|
|
41
|
-
// Fetch the related collection documents
|
|
42
|
-
const { data, isLoading } = useQuery({
|
|
43
|
-
queryKey: ["collection", relationTo, "picker", search],
|
|
44
|
-
queryFn: () => {
|
|
45
|
-
let qb = client!.collection(relationTo).find({ limit: 20 })
|
|
46
|
-
if (search) {
|
|
47
|
-
qb = qb.where({ [displayField]: { like: `%${search}%` } })
|
|
48
|
-
}
|
|
49
|
-
return qb.exec().then((res: any) => res.docs)
|
|
50
|
-
},
|
|
51
|
-
enabled: !!client && !!relationTo,
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
// Determine a display label for an item.
|
|
55
|
-
// We'll fallback to ID if no title or name exists.
|
|
56
|
-
const getDisplayLabel = (item: any) => {
|
|
57
|
-
return item[displayField] || item.name || item.slug || item.id
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const values = Array.isArray(value) ? value : value ? [value] : []
|
|
61
|
-
const selectedItems = values.map(v => data?.find((item: any) => item.id === v)).filter(Boolean)
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div className="dy-flex dy-flex-col dy-gap-2">
|
|
65
|
-
{label && <label className="dy-text-sm dy-font-medium dy-leading-none">{label}</label>}
|
|
66
|
-
<Popover open={disabled ? false : open} onOpenChange={setOpen}>
|
|
67
|
-
<PopoverTrigger asChild>
|
|
68
|
-
<Button
|
|
69
|
-
variant="outline"
|
|
70
|
-
role="combobox"
|
|
71
|
-
aria-expanded={open}
|
|
72
|
-
disabled={disabled}
|
|
73
|
-
className="dy-w-full dy-justify-between dy-font-normal"
|
|
74
|
-
>
|
|
75
|
-
{isLoading ? (
|
|
76
|
-
"Loading..."
|
|
77
|
-
) : selectedItems.length > 0 ? (
|
|
78
|
-
<div className="dy-flex dy-flex-wrap dy-gap-1">
|
|
79
|
-
{selectedItems.map((item: any) => (
|
|
80
|
-
<Badge key={item.id} variant="secondary" className="dy-text-[10px] dy-h-5 dy-px-1.5">
|
|
81
|
-
{getDisplayLabel(item)}
|
|
82
|
-
</Badge>
|
|
83
|
-
))}
|
|
84
|
-
</div>
|
|
85
|
-
) : (
|
|
86
|
-
<span className="dy-text-muted-foreground">Select {relationTo}...</span>
|
|
87
|
-
)}
|
|
88
|
-
<ChevronsUpDown className="dy-ml-2 dy-h-4 dy-w-4 dy-shrink-0 dy-opacity-50" />
|
|
89
|
-
</Button>
|
|
90
|
-
</PopoverTrigger>
|
|
91
|
-
<PopoverContent className="dy-w-[400px] dy-p-0" align="start">
|
|
92
|
-
<Command>
|
|
93
|
-
<CommandInput
|
|
94
|
-
placeholder={`Search ${relationTo}...`}
|
|
95
|
-
onValueChange={setSearch}
|
|
96
|
-
/>
|
|
97
|
-
<CommandList>
|
|
98
|
-
<CommandEmpty>{isLoading ? "Searching..." : "No item found."}</CommandEmpty>
|
|
99
|
-
<CommandGroup>
|
|
100
|
-
{data?.map((item: any) => (
|
|
101
|
-
<CommandItem
|
|
102
|
-
key={item.id}
|
|
103
|
-
value={item.id}
|
|
104
|
-
onSelect={() => {
|
|
105
|
-
if (multiple) {
|
|
106
|
-
const newValues = values.includes(item.id)
|
|
107
|
-
? values.filter(v => v !== item.id)
|
|
108
|
-
: [...values, item.id]
|
|
109
|
-
onChange(newValues)
|
|
110
|
-
} else {
|
|
111
|
-
onChange(item.id === value ? "" : item.id)
|
|
112
|
-
setOpen(false)
|
|
113
|
-
}
|
|
114
|
-
}}
|
|
115
|
-
>
|
|
116
|
-
<div className="dy-flex dy-items-center dy-gap-3 dy-flex-1">
|
|
117
|
-
{isUpload && (
|
|
118
|
-
<div className="dy-h-6 dy-w-6 dy-rounded dy-border dy-bg-muted dy-overflow-hidden dy-flex-shrink-0">
|
|
119
|
-
<img
|
|
120
|
-
src={getMediaUrl(item, client?.getBaseUrl() || "")}
|
|
121
|
-
className="dy-h-full dy-w-full dy-object-cover"
|
|
122
|
-
alt=""
|
|
123
|
-
/>
|
|
124
|
-
</div>
|
|
125
|
-
)}
|
|
126
|
-
<span className="dy-flex-1">{getDisplayLabel(item)}</span>
|
|
127
|
-
<Check
|
|
128
|
-
className={cn(
|
|
129
|
-
"dy-h-4 dy-w-4",
|
|
130
|
-
values.includes(item.id) ? "opacity-100" : "opacity-0"
|
|
131
|
-
)}
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
</CommandItem>
|
|
135
|
-
))}
|
|
136
|
-
</CommandGroup>
|
|
137
|
-
</CommandList>
|
|
138
|
-
</Command>
|
|
139
|
-
</PopoverContent>
|
|
140
|
-
</Popover>
|
|
141
|
-
</div>
|
|
142
|
-
)
|
|
143
|
-
}
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { useEditor, EditorContent, type Editor } from "@tiptap/react"
|
|
3
|
-
import StarterKit from "@tiptap/starter-kit"
|
|
4
|
-
import TextAlign from "@tiptap/extension-text-align"
|
|
5
|
-
import Image from "@tiptap/extension-image"
|
|
6
|
-
import { Toggle } from "../../ui/toggle"
|
|
7
|
-
import { cn } from "../../../lib/utils"
|
|
8
|
-
import { MediaPicker } from "./media-picker"
|
|
9
|
-
import {
|
|
10
|
-
Bold,
|
|
11
|
-
Italic,
|
|
12
|
-
Underline as UnderlineIcon,
|
|
13
|
-
Strikethrough,
|
|
14
|
-
List,
|
|
15
|
-
ListOrdered,
|
|
16
|
-
AlignLeft,
|
|
17
|
-
AlignCenter,
|
|
18
|
-
AlignRight,
|
|
19
|
-
Link as LinkIcon,
|
|
20
|
-
Heading1,
|
|
21
|
-
Heading2,
|
|
22
|
-
Quote
|
|
23
|
-
} from "lucide-react"
|
|
24
|
-
|
|
25
|
-
interface RichTextEditorProps {
|
|
26
|
-
value: string
|
|
27
|
-
onChange: (value: string) => void
|
|
28
|
-
label?: string
|
|
29
|
-
disabled?: boolean
|
|
30
|
-
collection?: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const MenuBar = ({ editor, collection = "media" }: { editor: Editor | null, collection?: string }) => {
|
|
34
|
-
if (!editor) {
|
|
35
|
-
return null
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const addLink = () => {
|
|
39
|
-
const url = window.prompt("URL")
|
|
40
|
-
if (url) {
|
|
41
|
-
editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run()
|
|
42
|
-
} else if (url === "") {
|
|
43
|
-
editor.chain().focus().extendMarkRange("link").unsetLink().run()
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<div className="dy-border dy-border-input dy-rounded-t-md dy-p-1 dy-flex dy-flex-wrap dy-gap-1 dy-items-center dy-bg-muted/50">
|
|
49
|
-
<Toggle
|
|
50
|
-
size="sm"
|
|
51
|
-
pressed={editor.isActive("bold")}
|
|
52
|
-
onPressedChange={() => editor.chain().focus().toggleBold().run()}
|
|
53
|
-
>
|
|
54
|
-
<Bold className="dy-h-4 dy-w-4" />
|
|
55
|
-
</Toggle>
|
|
56
|
-
<Toggle
|
|
57
|
-
size="sm"
|
|
58
|
-
pressed={editor.isActive("italic")}
|
|
59
|
-
onPressedChange={() => editor.chain().focus().toggleItalic().run()}
|
|
60
|
-
>
|
|
61
|
-
<Italic className="dy-h-4 dy-w-4" />
|
|
62
|
-
</Toggle>
|
|
63
|
-
<Toggle
|
|
64
|
-
size="sm"
|
|
65
|
-
pressed={editor.isActive("underline")}
|
|
66
|
-
onPressedChange={() => editor.chain().focus().toggleUnderline().run()}
|
|
67
|
-
>
|
|
68
|
-
<UnderlineIcon className="dy-h-4 dy-w-4" />
|
|
69
|
-
</Toggle>
|
|
70
|
-
<Toggle
|
|
71
|
-
size="sm"
|
|
72
|
-
pressed={editor.isActive("strike")}
|
|
73
|
-
onPressedChange={() => editor.chain().focus().toggleStrike().run()}
|
|
74
|
-
>
|
|
75
|
-
<Strikethrough className="dy-h-4 dy-w-4" />
|
|
76
|
-
</Toggle>
|
|
77
|
-
|
|
78
|
-
<div className="dy-w-[1px] dy-h-6 dy-bg-border dy-mx-1" />
|
|
79
|
-
|
|
80
|
-
<Toggle
|
|
81
|
-
size="sm"
|
|
82
|
-
pressed={editor.isActive("heading", { level: 1 })}
|
|
83
|
-
onPressedChange={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
|
84
|
-
>
|
|
85
|
-
<Heading1 className="dy-h-4 dy-w-4" />
|
|
86
|
-
</Toggle>
|
|
87
|
-
<Toggle
|
|
88
|
-
size="sm"
|
|
89
|
-
pressed={editor.isActive("heading", { level: 2 })}
|
|
90
|
-
onPressedChange={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
|
91
|
-
>
|
|
92
|
-
<Heading2 className="dy-h-4 dy-w-4" />
|
|
93
|
-
</Toggle>
|
|
94
|
-
<Toggle
|
|
95
|
-
size="sm"
|
|
96
|
-
pressed={editor.isActive("bulletList")}
|
|
97
|
-
onPressedChange={() => editor.chain().focus().toggleBulletList().run()}
|
|
98
|
-
>
|
|
99
|
-
<List className="dy-h-4 dy-w-4" />
|
|
100
|
-
</Toggle>
|
|
101
|
-
<Toggle
|
|
102
|
-
size="sm"
|
|
103
|
-
pressed={editor.isActive("orderedList")}
|
|
104
|
-
onPressedChange={() => editor.chain().focus().toggleOrderedList().run()}
|
|
105
|
-
>
|
|
106
|
-
<ListOrdered className="dy-h-4 dy-w-4" />
|
|
107
|
-
</Toggle>
|
|
108
|
-
<Toggle
|
|
109
|
-
size="sm"
|
|
110
|
-
pressed={editor.isActive("blockquote")}
|
|
111
|
-
onPressedChange={() => editor.chain().focus().toggleBlockquote().run()}
|
|
112
|
-
>
|
|
113
|
-
<Quote className="dy-h-4 dy-w-4" />
|
|
114
|
-
</Toggle>
|
|
115
|
-
|
|
116
|
-
<div className="dy-w-[1px] dy-h-6 dy-bg-border dy-mx-1" />
|
|
117
|
-
|
|
118
|
-
<Toggle
|
|
119
|
-
size="sm"
|
|
120
|
-
pressed={editor.isActive({ textAlign: "left" })}
|
|
121
|
-
onPressedChange={() => editor.chain().focus().setTextAlign("left").run()}
|
|
122
|
-
>
|
|
123
|
-
<AlignLeft className="dy-h-4 dy-w-4" />
|
|
124
|
-
</Toggle>
|
|
125
|
-
<Toggle
|
|
126
|
-
size="sm"
|
|
127
|
-
pressed={editor.isActive({ textAlign: "center" })}
|
|
128
|
-
onPressedChange={() => editor.chain().focus().setTextAlign("center").run()}
|
|
129
|
-
>
|
|
130
|
-
<AlignCenter className="dy-h-4 dy-w-4" />
|
|
131
|
-
</Toggle>
|
|
132
|
-
<Toggle
|
|
133
|
-
size="sm"
|
|
134
|
-
pressed={editor.isActive({ textAlign: "right" })}
|
|
135
|
-
onPressedChange={() => editor.chain().focus().setTextAlign("right").run()}
|
|
136
|
-
>
|
|
137
|
-
<AlignRight className="dy-h-4 dy-w-4" />
|
|
138
|
-
</Toggle>
|
|
139
|
-
|
|
140
|
-
<div className="dy-w-[1px] dy-h-6 dy-bg-border dy-mx-1" />
|
|
141
|
-
|
|
142
|
-
<Toggle
|
|
143
|
-
size="sm"
|
|
144
|
-
pressed={editor.isActive("link")}
|
|
145
|
-
onPressedChange={addLink}
|
|
146
|
-
>
|
|
147
|
-
<LinkIcon className="dy-h-4 dy-w-4" />
|
|
148
|
-
</Toggle>
|
|
149
|
-
|
|
150
|
-
<div className="dy-ml-auto">
|
|
151
|
-
<MediaPicker
|
|
152
|
-
collection={collection}
|
|
153
|
-
variant="icon"
|
|
154
|
-
onChange={(val) => {
|
|
155
|
-
const filename = Array.isArray(val) ? val[0] : val
|
|
156
|
-
if (filename) {
|
|
157
|
-
const url = `/api/media/${filename}`
|
|
158
|
-
editor.chain().focus().setImage({ src: url, alt: filename }).run()
|
|
159
|
-
}
|
|
160
|
-
}}
|
|
161
|
-
/>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export function RichTextEditor({ value, onChange, label, disabled, collection = "media" }: RichTextEditorProps) {
|
|
168
|
-
const editor = useEditor({
|
|
169
|
-
extensions: [
|
|
170
|
-
StarterKit.configure({
|
|
171
|
-
link: {
|
|
172
|
-
openOnClick: false,
|
|
173
|
-
},
|
|
174
|
-
}),
|
|
175
|
-
TextAlign.configure({
|
|
176
|
-
types: ["heading", "paragraph"],
|
|
177
|
-
}),
|
|
178
|
-
Image.configure({
|
|
179
|
-
HTMLAttributes: {
|
|
180
|
-
class: "rounded-md max-w-full h-auto my-4",
|
|
181
|
-
},
|
|
182
|
-
}),
|
|
183
|
-
],
|
|
184
|
-
content: value,
|
|
185
|
-
editable: !disabled,
|
|
186
|
-
immediatelyRender: false,
|
|
187
|
-
onUpdate: ({ editor }) => {
|
|
188
|
-
// Extract HTML for standard rich text storage
|
|
189
|
-
onChange(editor.getHTML())
|
|
190
|
-
},
|
|
191
|
-
editorProps: {
|
|
192
|
-
attributes: {
|
|
193
|
-
class: "prose prose-sm dark:prose-invert max-w-none min-h-[150px] p-4 focus:outline-none border border-t-0 rounded-b-md border-input bg-transparent",
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
React.useEffect(() => {
|
|
199
|
-
if (editor) {
|
|
200
|
-
editor.setEditable(!disabled)
|
|
201
|
-
}
|
|
202
|
-
}, [disabled, editor])
|
|
203
|
-
|
|
204
|
-
// Update editor content if value changes externally (e.g. initial load)
|
|
205
|
-
React.useEffect(() => {
|
|
206
|
-
if (editor && value !== editor.getHTML()) {
|
|
207
|
-
// Prevent cursor jump by checking if the content is actually different text-wise
|
|
208
|
-
const currentHtml = editor.getHTML()
|
|
209
|
-
if (currentHtml !== value && value) {
|
|
210
|
-
editor.commands.setContent(value)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}, [value, editor])
|
|
214
|
-
|
|
215
|
-
return (
|
|
216
|
-
<div className="dy-space-y-2">
|
|
217
|
-
{label && <label className="dy-text-sm dy-font-medium dy-leading-none dy-peer-disabled:dy-cursor-not-allowed dy-peer-disabled:dy-opacity-70">{label}</label>}
|
|
218
|
-
<div className="dy-flex dy-flex-col dy-w-full">
|
|
219
|
-
{!disabled && <MenuBar editor={editor} collection={collection} />}
|
|
220
|
-
<EditorContent editor={editor} className={cn(disabled && "dy-opacity-80")} />
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
)
|
|
224
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Select,
|
|
3
|
-
SelectContent,
|
|
4
|
-
SelectItem,
|
|
5
|
-
SelectTrigger,
|
|
6
|
-
SelectValue,
|
|
7
|
-
} from "../../ui/select"
|
|
8
|
-
import { normalizeOptions } from "../utils"
|
|
9
|
-
import type { Field as FieldSchema } from "@dyrected/sdk"
|
|
10
|
-
|
|
11
|
-
interface SelectFieldProps {
|
|
12
|
-
schema: FieldSchema
|
|
13
|
-
field: any
|
|
14
|
-
disabled?: boolean
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function SelectField({ schema, field, disabled }: SelectFieldProps) {
|
|
18
|
-
const label = schema.label || schema.name.charAt(0).toUpperCase() + schema.name.slice(1)
|
|
19
|
-
const options = normalizeOptions(schema.options)
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<Select onValueChange={field.onChange} defaultValue={field.value} disabled={disabled}>
|
|
23
|
-
<SelectTrigger className="dy-h-12 dy-rounded-xl dy-border-border/40 dy-bg-white/50 focus:dy-ring-0 focus:dy-ring-offset-0 focus:dy-bg-white dy-shadow-sm dy-transition-all hover:dy-shadow-md">
|
|
24
|
-
<SelectValue placeholder={schema.admin?.placeholder || `Select ${label.toLowerCase()}`} />
|
|
25
|
-
</SelectTrigger>
|
|
26
|
-
<SelectContent className="dy-rounded-xl dy-border-border/40 dy-shadow-xl dy-animate-in dy-fade-in dy-zoom-in-95">
|
|
27
|
-
{options.map((opt) => (
|
|
28
|
-
<SelectItem key={opt.value} value={opt.value} className="dy-rounded-lg focus:dy-bg-primary/5 focus:dy-text-primary dy-transition-colors">
|
|
29
|
-
{opt.label}
|
|
30
|
-
</SelectItem>
|
|
31
|
-
))}
|
|
32
|
-
</SelectContent>
|
|
33
|
-
</Select>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Switch } from "../../ui/switch"
|
|
2
|
-
|
|
3
|
-
interface SwitchFieldProps {
|
|
4
|
-
field: any
|
|
5
|
-
disabled?: boolean
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function SwitchField({ field, disabled }: SwitchFieldProps) {
|
|
9
|
-
return (
|
|
10
|
-
<Switch
|
|
11
|
-
checked={field.value}
|
|
12
|
-
onCheckedChange={field.onChange}
|
|
13
|
-
disabled={disabled}
|
|
14
|
-
/>
|
|
15
|
-
)
|
|
16
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Textarea } from "../../ui/textarea"
|
|
2
|
-
import type { Field as FieldSchema } from "@dyrected/sdk"
|
|
3
|
-
|
|
4
|
-
interface TextAreaFieldProps {
|
|
5
|
-
schema: FieldSchema
|
|
6
|
-
field: any
|
|
7
|
-
disabled?: boolean
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function TextAreaField({ schema, field, disabled }: TextAreaFieldProps) {
|
|
11
|
-
const label = schema.label || schema.name.charAt(0).toUpperCase() + schema.name.slice(1)
|
|
12
|
-
const placeholder = schema.admin?.placeholder || `Enter ${label.toLowerCase()}...`
|
|
13
|
-
|
|
14
|
-
return <Textarea {...field} value={field.value ?? ""} placeholder={placeholder} disabled={disabled} />
|
|
15
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Input } from "../../ui/input"
|
|
2
|
-
import type { Field as FieldSchema } from "@dyrected/sdk"
|
|
3
|
-
|
|
4
|
-
interface TextFieldProps {
|
|
5
|
-
schema: FieldSchema
|
|
6
|
-
field: any
|
|
7
|
-
disabled?: boolean
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function TextField({ schema, field, disabled }: TextFieldProps) {
|
|
11
|
-
const label = schema.label || schema.name.charAt(0).toUpperCase() + schema.name.slice(1)
|
|
12
|
-
const placeholder = schema.admin?.placeholder || `Enter ${label.toLowerCase()}...`
|
|
13
|
-
|
|
14
|
-
switch (schema.type) {
|
|
15
|
-
case "number":
|
|
16
|
-
return <Input type="number" {...field} value={field.value ?? ""} placeholder={schema.admin?.placeholder || "0"} disabled={disabled} />
|
|
17
|
-
case "email":
|
|
18
|
-
return <Input type="email" {...field} value={field.value ?? ""} placeholder={placeholder} disabled={disabled} />
|
|
19
|
-
case "url":
|
|
20
|
-
return <Input type="url" {...field} value={field.value ?? ""} placeholder={schema.admin?.placeholder || "https://"} disabled={disabled} />
|
|
21
|
-
default:
|
|
22
|
-
return <Input {...field} value={field.value ?? ""} placeholder={placeholder} disabled={disabled} />
|
|
23
|
-
}
|
|
24
|
-
}
|