betterstart-cli 0.0.17 → 0.0.19

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 (28) hide show
  1. package/dist/assets/adapters/next/plugins/blog/schemas/menus.json +1 -0
  2. package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +1 -0
  3. package/dist/assets/adapters/next/plugins/portfolio/plugin.ts +17 -0
  4. package/dist/assets/adapters/next/plugins/portfolio/schemas/portfolio.json +128 -0
  5. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +2 -2
  6. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/snippets-tab.tsx +1 -0
  7. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +1 -1
  8. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-dialog.tsx +1 -1
  9. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-drawer.tsx +1 -1
  10. package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-item.tsx +17 -7
  11. package/dist/assets/adapters/next/templates/init/components/shared/media/media-preview.tsx +1 -0
  12. package/dist/assets/adapters/next/templates/init/data/navigation.ts +2 -1
  13. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-source-mode.tsx +3 -3
  14. package/dist/assets/adapters/next/templates/init/hooks/use-media.ts +2 -1
  15. package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +3 -3
  16. package/dist/assets/shared-assets/react-admin/custom/content-editor/media-gallery-block.tsx +2 -2
  17. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-add-controls.tsx +2 -2
  18. package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +219 -40
  19. package/dist/assets/shared-assets/react-admin/custom/media-field-empty-state.tsx +99 -0
  20. package/dist/assets/shared-assets/react-admin/custom/media-field.tsx +181 -0
  21. package/dist/assets/shared-assets/react-admin/custom/sortable-gallery-item.tsx +38 -38
  22. package/dist/assets/shared-assets/react-admin/schema.json +64 -2
  23. package/dist/assets/shared-assets/react-admin/ui/pagination.tsx +1 -1
  24. package/dist/cli.js +474 -143
  25. package/dist/cli.js.map +1 -1
  26. package/package.json +1 -1
  27. package/dist/assets/shared-assets/react-admin/custom/gallery-thumbnail.tsx +0 -39
  28. package/dist/assets/shared-assets/react-admin/custom/media-gallery-field.tsx +0 -182
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "betterstart-cli",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Scaffold a plugin-based Admin into any Next.js 16 application",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,39 +0,0 @@
1
- import type { AdminMedia } from '@admin/types'
2
- import { FileText, Film } from 'lucide-react'
3
- import Image from 'next/image'
4
-
5
- export function GalleryThumbnail({ media }: { media: AdminMedia }) {
6
- if (media.contentType.startsWith('image/')) {
7
- return (
8
- <Image
9
- src={media.url}
10
- alt={media.alt || media.filename}
11
- fill
12
- sizes="(max-width: 640px) 33vw, (max-width: 768px) 25vw, 160px"
13
- className="object-cover"
14
- loading="lazy"
15
- draggable={false}
16
- />
17
- )
18
- }
19
-
20
- if (media.contentType.startsWith('video/')) {
21
- return (
22
- <div className="flex size-full flex-col items-center justify-center gap-1">
23
- <Film className="size-6 text-foreground" />
24
- <span className="max-w-full truncate px-1 text-[9px] text-muted-foreground">
25
- {media.filename}
26
- </span>
27
- </div>
28
- )
29
- }
30
-
31
- return (
32
- <div className="flex size-full flex-col items-center justify-center gap-1">
33
- <FileText className="size-6 text-foreground" />
34
- <span className="max-w-full truncate px-1 text-[9px] text-muted-foreground">
35
- {media.filename}
36
- </span>
37
- </div>
38
- )
39
- }
@@ -1,182 +0,0 @@
1
- 'use client'
2
-
3
- import { MediaGalleryDialog } from '@admin/components/shared/media/media-gallery-dialog'
4
- import { MediaPreview } from '@admin/components/shared/media/media-preview'
5
- import { Button } from '@admin/components/ui/button'
6
- import { Card, CardContent, CardFooter } from '@admin/components/ui/card'
7
- import { useCopyToClipboard } from '@admin/hooks/use-copy-to-clipboard'
8
- import { useMediaById } from '@admin/hooks/use-media'
9
- import type { AdminMedia } from '@admin/types'
10
- import { createAdminMediaFromUrl } from '@admin/utils/media/fallback'
11
- import { cn } from '@admin/utils/shared/cn'
12
- import { Check, Copy, ImageIcon, Link2 } from 'lucide-react'
13
- import Link from 'next/link'
14
- import * as React from 'react'
15
-
16
- type MediaGalleryFieldValueMode = 'id' | 'url'
17
-
18
- interface MediaGalleryFieldProps {
19
- value?: string
20
- onChange: (value: string) => void
21
- onBlur?: () => void
22
- onRemove?: () => void
23
- accept?: string
24
- disabled?: boolean
25
- className?: string
26
- isRemovable?: boolean
27
- valueMode?: MediaGalleryFieldValueMode
28
- }
29
-
30
- export function MediaGalleryField({
31
- value,
32
- onChange,
33
- onBlur,
34
- onRemove,
35
- accept,
36
- disabled,
37
- className,
38
- isRemovable = false,
39
- valueMode = 'id'
40
- }: MediaGalleryFieldProps) {
41
- const [open, setOpen] = React.useState(false)
42
- const { data: media } = useMediaById(valueMode === 'id' ? value : undefined)
43
- const [copyMediaUrl, hasCopiedMediaUrl] = useCopyToClipboard()
44
- const selectedMedia =
45
- media ?? (valueMode === 'url' && value ? createAdminMediaFromUrl(value, accept) : null)
46
-
47
- function handleSelect(selected: AdminMedia | AdminMedia[]) {
48
- const item = Array.isArray(selected) ? selected[0] : selected
49
- if (item) {
50
- onChange(valueMode === 'url' ? item.url : item.id)
51
- }
52
- }
53
-
54
- function handleRemove(e: React.MouseEvent) {
55
- e.preventDefault()
56
- e.stopPropagation()
57
- if (isRemovable && onRemove) {
58
- onRemove()
59
- return
60
- }
61
- onChange('')
62
- onBlur?.()
63
- }
64
-
65
- function handleAddImage(e: React.MouseEvent) {
66
- e.preventDefault()
67
- e.stopPropagation()
68
- setOpen(true)
69
- }
70
-
71
- if (selectedMedia?.url) {
72
- return (
73
- <div className={cn('group relative', className)}>
74
- <Card className="p-0">
75
- <CardContent className="p-0! relative">
76
- <div className="relative aspect-video rounded-lg bg-background overflow-hidden">
77
- <MediaPreview media={selectedMedia} showMetadata={Boolean(media)} />
78
- </div>
79
-
80
- {!disabled && (
81
- <>
82
- <div
83
- aria-hidden="true"
84
- className="pointer-events-none absolute inset-0 bg-black/10 opacity-0 backdrop-blur-xs transition-opacity duration-300 ease-out group-hover:opacity-100 rounded-lg"
85
- />
86
- <div className="absolute inset-0 flex items-center justify-center gap-1 opacity-0 transition-opacity duration-200 ease-out group-hover:opacity-100">
87
- <Button
88
- type="button"
89
- variant="outline"
90
- onClick={() => setOpen(true)}
91
- className="border-primary/10"
92
- >
93
- Change
94
- </Button>
95
- <Button type="button" variant="default" onClick={handleRemove}>
96
- Remove
97
- </Button>
98
- </div>
99
- </>
100
- )}
101
- </CardContent>
102
- <CardFooter className="grid w-full gap-0.5 px-4">
103
- <div className="flex w-full min-w-0 items-center gap-x-2">
104
- <div className="flex min-w-0 flex-1 items-center gap-x-2 text-muted-foreground">
105
- <Link2 className="size-4 shrink-0" />
106
- <Link
107
- href={selectedMedia.url}
108
- target="_blank"
109
- rel="noopener noreferrer"
110
- className="min-w-0 truncate text-foreground hover:underline underline-offset-4"
111
- >
112
- {selectedMedia.url}
113
- </Link>
114
- </div>
115
- <Button
116
- type="button"
117
- aria-label={hasCopiedMediaUrl ? 'Media URL copied' : 'Copy media URL'}
118
- variant="ghost"
119
- title={hasCopiedMediaUrl ? 'Copied' : 'Copy media URL'}
120
- onClick={() => copyMediaUrl(selectedMedia.url)}
121
- className="shrink-0 size-7"
122
- >
123
- {hasCopiedMediaUrl ? <Check className="text-foreground" /> : <Copy />}
124
- </Button>
125
- </div>
126
- </CardFooter>
127
- </Card>
128
-
129
- <MediaGalleryDialog
130
- open={open}
131
- onOpenChange={setOpen}
132
- onSelect={handleSelect}
133
- mode="single"
134
- accept={accept}
135
- />
136
- </div>
137
- )
138
- }
139
-
140
- return (
141
- <div className={className}>
142
- <Card
143
- className={cn('group p-0 gap-0', {
144
- 'cursor-not-allowed opacity-50 pointer-events-none': disabled
145
- })}
146
- onClick={() => !disabled && setOpen(true)}
147
- >
148
- <CardContent className="relative p-0 justify-center items-center flex flex-col h-40 hover:bg-muted/50 transition-colors duration-200">
149
- <ImageIcon
150
- className={cn('text-muted-foreground transition-opacity', {
151
- 'group-hover:opacity-0 group-focus-within:opacity-0': isRemovable && !disabled
152
- })}
153
- strokeWidth={1}
154
- />
155
- {isRemovable && !disabled && (
156
- <div className="absolute inset-0 flex items-center justify-center gap-1 opacity-0 transition-opacity duration-200 ease-out group-hover:opacity-100 group-focus-within:opacity-100">
157
- <Button
158
- type="button"
159
- variant="outline"
160
- onClick={handleAddImage}
161
- className="border-primary/10"
162
- >
163
- Add Image
164
- </Button>
165
- <Button type="button" variant="default" onClick={handleRemove}>
166
- Remove
167
- </Button>
168
- </div>
169
- )}
170
- </CardContent>
171
- </Card>
172
-
173
- <MediaGalleryDialog
174
- open={open}
175
- onOpenChange={setOpen}
176
- onSelect={handleSelect}
177
- mode="single"
178
- accept={accept}
179
- />
180
- </div>
181
- )
182
- }