@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.
Files changed (170) hide show
  1. package/dist/App.d.ts +1 -0
  2. package/dist/admin.css +2 -0
  3. package/dist/components/auth/auth-gate.d.ts +13 -0
  4. package/dist/components/error-boundary.d.ts +16 -0
  5. package/dist/components/forms/field-renderer.d.ts +22 -0
  6. package/dist/components/forms/fields/block-builder.d.ts +9 -0
  7. package/dist/components/forms/fields/date-picker.d.ts +8 -0
  8. package/dist/components/forms/fields/json-editor.d.ts +8 -0
  9. package/dist/components/forms/fields/media-picker.d.ts +12 -0
  10. package/dist/components/forms/fields/multi-select.d.ts +19 -0
  11. package/dist/components/forms/fields/radio-field.d.ts +8 -0
  12. package/dist/components/forms/fields/relationship-picker.d.ts +10 -0
  13. package/dist/components/forms/fields/rich-text-editor.d.ts +9 -0
  14. package/dist/components/forms/fields/select-field.d.ts +8 -0
  15. package/dist/components/forms/fields/switch-field.d.ts +6 -0
  16. package/dist/components/forms/fields/text-area-field.d.ts +8 -0
  17. package/dist/components/forms/fields/text-field.d.ts +8 -0
  18. package/dist/components/forms/form-engine.d.ts +14 -0
  19. package/dist/components/forms/form-field-renderer.d.ts +20 -0
  20. package/dist/components/forms/utils.d.ts +11 -0
  21. package/dist/components/layout/admin-shell.d.ts +5 -0
  22. package/dist/components/layout/branding-provider.d.ts +4 -0
  23. package/dist/components/live-preview/LivePreviewPane.d.ts +7 -0
  24. package/dist/components/media/focal-point-picker.d.ts +12 -0
  25. package/dist/components/media/media-card.d.ts +8 -0
  26. package/dist/components/media/media-grid.d.ts +8 -0
  27. package/dist/components/media/media-library-dialog.d.ts +11 -0
  28. package/dist/components/ui/aspect-ratio.d.ts +3 -0
  29. package/dist/components/ui/badge.d.ts +9 -0
  30. package/dist/components/ui/button.d.ts +11 -0
  31. package/dist/components/ui/calendar.d.ts +8 -0
  32. package/dist/components/ui/card.d.ts +8 -0
  33. package/dist/components/ui/checkbox.d.ts +4 -0
  34. package/dist/components/ui/command.d.ts +80 -0
  35. package/dist/components/ui/data-table.d.ts +14 -0
  36. package/dist/components/ui/dialog.d.ts +19 -0
  37. package/dist/components/ui/dropdown-menu.d.ts +27 -0
  38. package/dist/components/ui/form.d.ts +23 -0
  39. package/dist/components/ui/input.d.ts +3 -0
  40. package/dist/components/ui/label.d.ts +5 -0
  41. package/dist/components/ui/page-header.d.ts +10 -0
  42. package/dist/components/ui/pagination.d.ts +11 -0
  43. package/dist/components/ui/popover.d.ts +6 -0
  44. package/dist/components/ui/progress.d.ts +4 -0
  45. package/dist/components/ui/radio-group.d.ts +5 -0
  46. package/dist/components/ui/render-cell.d.ts +8 -0
  47. package/dist/components/ui/scroll-area.d.ts +5 -0
  48. package/dist/components/ui/select.d.ts +13 -0
  49. package/dist/components/ui/separator.d.ts +4 -0
  50. package/dist/components/ui/sheet.d.ts +25 -0
  51. package/dist/components/ui/sidebar.d.ts +65 -0
  52. package/dist/components/ui/skeleton.d.ts +2 -0
  53. package/dist/components/ui/sonner.d.ts +4 -0
  54. package/dist/components/ui/switch.d.ts +4 -0
  55. package/dist/components/ui/table.d.ts +10 -0
  56. package/dist/components/ui/tabs.d.ts +7 -0
  57. package/dist/components/ui/textarea.d.ts +3 -0
  58. package/dist/components/ui/toggle.d.ts +12 -0
  59. package/dist/components/ui/tooltip.d.ts +7 -0
  60. package/dist/hooks/use-mobile.d.ts +1 -0
  61. package/dist/hooks/use-preferences.d.ts +6 -0
  62. package/dist/index.d.ts +38 -0
  63. package/dist/index.mjs +69091 -0
  64. package/dist/lib/utils.d.ts +3 -0
  65. package/dist/main.d.ts +0 -0
  66. package/dist/pages/auth/first-user-page.d.ts +4 -0
  67. package/dist/pages/auth/login-page.d.ts +4 -0
  68. package/dist/pages/collections/edit-page.d.ts +1 -0
  69. package/dist/pages/collections/list-page.d.ts +5 -0
  70. package/dist/pages/dashboard/dashboard.d.ts +1 -0
  71. package/dist/pages/globals/editor-page.d.ts +1 -0
  72. package/dist/pages/media/media-page.d.ts +4 -0
  73. package/dist/pages/setup/setup-prompt.d.ts +6 -0
  74. package/dist/providers/dyrected-provider.d.ts +29 -0
  75. package/dist/providers/query-provider.d.ts +3 -0
  76. package/package.json +6 -3
  77. package/CHANGELOG.md +0 -153
  78. package/components.json +0 -17
  79. package/eslint.config.js +0 -22
  80. package/index.html +0 -13
  81. package/postcss.config.js +0 -6
  82. package/scripts/prefix-tailwind-precision.py +0 -98
  83. package/scripts/prefix-tailwind.py +0 -67
  84. package/src/App.css +0 -184
  85. package/src/App.tsx +0 -25
  86. package/src/assets/dyrected.svg +0 -155
  87. package/src/assets/hero.png +0 -0
  88. package/src/assets/react.svg +0 -1
  89. package/src/assets/vite.svg +0 -1
  90. package/src/components/auth/auth-gate.tsx +0 -64
  91. package/src/components/error-boundary.tsx +0 -45
  92. package/src/components/forms/field-renderer.tsx +0 -111
  93. package/src/components/forms/fields/block-builder.tsx +0 -213
  94. package/src/components/forms/fields/date-picker.tsx +0 -60
  95. package/src/components/forms/fields/json-editor.tsx +0 -62
  96. package/src/components/forms/fields/media-picker.tsx +0 -286
  97. package/src/components/forms/fields/multi-select.tsx +0 -145
  98. package/src/components/forms/fields/radio-field.tsx +0 -51
  99. package/src/components/forms/fields/relationship-picker.tsx +0 -143
  100. package/src/components/forms/fields/rich-text-editor.tsx +0 -224
  101. package/src/components/forms/fields/select-field.tsx +0 -35
  102. package/src/components/forms/fields/switch-field.tsx +0 -16
  103. package/src/components/forms/fields/text-area-field.tsx +0 -15
  104. package/src/components/forms/fields/text-field.tsx +0 -24
  105. package/src/components/forms/form-engine.tsx +0 -87
  106. package/src/components/forms/form-field-renderer.tsx +0 -269
  107. package/src/components/forms/utils.ts +0 -97
  108. package/src/components/layout/admin-shell.tsx +0 -479
  109. package/src/components/layout/branding-provider.tsx +0 -112
  110. package/src/components/live-preview/LivePreviewPane.tsx +0 -128
  111. package/src/components/media/focal-point-picker.tsx +0 -66
  112. package/src/components/media/media-card.tsx +0 -44
  113. package/src/components/media/media-grid.tsx +0 -32
  114. package/src/components/media/media-library-dialog.tsx +0 -465
  115. package/src/components/ui/aspect-ratio.tsx +0 -7
  116. package/src/components/ui/badge.tsx +0 -36
  117. package/src/components/ui/button.tsx +0 -56
  118. package/src/components/ui/calendar.tsx +0 -214
  119. package/src/components/ui/card.tsx +0 -79
  120. package/src/components/ui/checkbox.tsx +0 -28
  121. package/src/components/ui/command.tsx +0 -151
  122. package/src/components/ui/data-table.tsx +0 -219
  123. package/src/components/ui/dialog.tsx +0 -122
  124. package/src/components/ui/dropdown-menu.tsx +0 -200
  125. package/src/components/ui/form.tsx +0 -178
  126. package/src/components/ui/input.tsx +0 -24
  127. package/src/components/ui/label.tsx +0 -24
  128. package/src/components/ui/page-header.tsx +0 -30
  129. package/src/components/ui/pagination.tsx +0 -57
  130. package/src/components/ui/popover.tsx +0 -29
  131. package/src/components/ui/progress.tsx +0 -26
  132. package/src/components/ui/radio-group.tsx +0 -42
  133. package/src/components/ui/render-cell.tsx +0 -110
  134. package/src/components/ui/scroll-area.tsx +0 -46
  135. package/src/components/ui/select.tsx +0 -160
  136. package/src/components/ui/separator.tsx +0 -29
  137. package/src/components/ui/sheet.tsx +0 -140
  138. package/src/components/ui/sidebar.tsx +0 -771
  139. package/src/components/ui/skeleton.tsx +0 -15
  140. package/src/components/ui/sonner.tsx +0 -27
  141. package/src/components/ui/switch.tsx +0 -27
  142. package/src/components/ui/table.tsx +0 -117
  143. package/src/components/ui/tabs.tsx +0 -53
  144. package/src/components/ui/textarea.tsx +0 -22
  145. package/src/components/ui/toggle.tsx +0 -43
  146. package/src/components/ui/tooltip.tsx +0 -28
  147. package/src/hooks/use-mobile.tsx +0 -19
  148. package/src/hooks/use-preferences.ts +0 -56
  149. package/src/index.css +0 -111
  150. package/src/index.tsx +0 -198
  151. package/src/lib/utils.ts +0 -36
  152. package/src/main.tsx +0 -10
  153. package/src/pages/auth/first-user-page.tsx +0 -115
  154. package/src/pages/auth/login-page.tsx +0 -91
  155. package/src/pages/collections/edit-page.tsx +0 -280
  156. package/src/pages/collections/list-page.tsx +0 -343
  157. package/src/pages/dashboard/dashboard.tsx +0 -150
  158. package/src/pages/globals/editor-page.tsx +0 -122
  159. package/src/pages/media/media-page.tsx +0 -564
  160. package/src/pages/setup/setup-prompt.tsx +0 -181
  161. package/src/providers/dyrected-provider.tsx +0 -122
  162. package/src/providers/query-provider.tsx +0 -19
  163. package/src/types/jexl.d.ts +0 -11
  164. package/tailwind.config.ts +0 -103
  165. package/tsconfig.app.json +0 -28
  166. package/tsconfig.json +0 -12
  167. package/tsconfig.node.json +0 -25
  168. package/vite.config.ts +0 -39
  169. /package/{public → dist}/favicon.svg +0 -0
  170. /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
- }