@gentleduck/registry-ui 0.2.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/CHANGELOG.md +62 -0
- package/index.css +3 -0
- package/package.json +59 -0
- package/src/_old/_table/index.ts +5 -0
- package/src/_old/_table/table-advanced.constants.tsx +24 -0
- package/src/_old/_table/table-advanced.tsx +311 -0
- package/src/_old/_table/table-advanced.types.ts +272 -0
- package/src/_old/_table/table.constants.ts +2 -0
- package/src/_old/_table/table.hook.tsx +115 -0
- package/src/_old/_table/table.lib.ts +85 -0
- package/src/_old/_table/table.tsx +916 -0
- package/src/_old/_table/table.types.ts +118 -0
- package/src/_old/_table/todo.md +11 -0
- package/src/_old/_upload/index.ts +9 -0
- package/src/_old/_upload/todo.md +38 -0
- package/src/_old/_upload/upload-advanced-chunks.tsx +1624 -0
- package/src/_old/_upload/upload-advanced.tsx +507 -0
- package/src/_old/_upload/upload-sonner.tsx +58 -0
- package/src/_old/_upload/upload.assets.tsx +239 -0
- package/src/_old/_upload/upload.constants.tsx +75 -0
- package/src/_old/_upload/upload.dto.ts +19 -0
- package/src/_old/_upload/upload.lib.tsx +630 -0
- package/src/_old/_upload/upload.tsx +491 -0
- package/src/_old/_upload/upload.types.ts +436 -0
- package/src/accordion/accordion.tsx +247 -0
- package/src/accordion/index.ts +1 -0
- package/src/alert/alert.constants.ts +17 -0
- package/src/alert/alert.tsx +52 -0
- package/src/alert/index.ts +2 -0
- package/src/alert-dialog/alert-dialog.tsx +107 -0
- package/src/alert-dialog/index.ts +1 -0
- package/src/aspect-ratio/aspect-ratio.tsx +33 -0
- package/src/aspect-ratio/index.ts +1 -0
- package/src/audio/audio-record.tsx +776 -0
- package/src/audio/audio-visualizer.tsx +377 -0
- package/src/audio/audio.libs.ts +5 -0
- package/src/audio/audio.types.ts +50 -0
- package/src/audio/index.ts +2 -0
- package/src/avatar/avatar.tsx +78 -0
- package/src/avatar/index.ts +1 -0
- package/src/badge/badge.constants.ts +38 -0
- package/src/badge/badge.tsx +19 -0
- package/src/badge/index.ts +2 -0
- package/src/breadcrumb/breadcrumb.tsx +119 -0
- package/src/breadcrumb/index.ts +1 -0
- package/src/button/button.constants.ts +44 -0
- package/src/button/button.tsx +79 -0
- package/src/button/button.types.ts +38 -0
- package/src/button/index.ts +3 -0
- package/src/button-group/button-group.constants.ts +26 -0
- package/src/button-group/button-group.tsx +65 -0
- package/src/button-group/index.ts +2 -0
- package/src/calendar/calendar.tsx +191 -0
- package/src/calendar/index.ts +1 -0
- package/src/card/card.tsx +81 -0
- package/src/card/index.ts +1 -0
- package/src/carousel/carousel.tsx +211 -0
- package/src/carousel/carousel.types.ts +23 -0
- package/src/carousel/index.ts +2 -0
- package/src/chart/chart.libs.ts +27 -0
- package/src/chart/chart.tsx +260 -0
- package/src/chart/chart.types.ts +38 -0
- package/src/chart/index.ts +3 -0
- package/src/checkbox/checkbox.tsx +144 -0
- package/src/checkbox/checkbox.types.ts +24 -0
- package/src/checkbox/index.ts +2 -0
- package/src/collapsible/collapsible.tsx +151 -0
- package/src/collapsible/index.ts +1 -0
- package/src/combobox/combobox.tsx +132 -0
- package/src/combobox/index.ts +1 -0
- package/src/command/command.tsx +192 -0
- package/src/command/command.types.ts +11 -0
- package/src/command/index.ts +2 -0
- package/src/context-menu/context-menu.tsx +178 -0
- package/src/context-menu/index.ts +1 -0
- package/src/dialog/dialog-responsive.tsx +137 -0
- package/src/dialog/dialog.tsx +97 -0
- package/src/dialog/index.ts +2 -0
- package/src/direction/direction.tsx +13 -0
- package/src/direction/index.ts +1 -0
- package/src/drawer/drawer.tsx +185 -0
- package/src/drawer/index.ts +1 -0
- package/src/dropdown-menu/dropdown-menu.tsx +181 -0
- package/src/dropdown-menu/index.ts +1 -0
- package/src/empty/empty.constants.ts +15 -0
- package/src/empty/empty.tsx +73 -0
- package/src/empty/index.ts +2 -0
- package/src/field/field.constants.ts +22 -0
- package/src/field/field.tsx +203 -0
- package/src/field/index.ts +2 -0
- package/src/hover-card/hover-card.tsx +79 -0
- package/src/hover-card/index.ts +1 -0
- package/src/input/index.ts +1 -0
- package/src/input/input.tsx +45 -0
- package/src/input-group/index.ts +1 -0
- package/src/input-group/input-group.tsx +170 -0
- package/src/input-otp/index.ts +1 -0
- package/src/input-otp/input-otp.tsx +66 -0
- package/src/item/index.ts +2 -0
- package/src/item/item.constants.ts +22 -0
- package/src/item/item.tsx +185 -0
- package/src/json-editor/index.ts +4 -0
- package/src/json-editor/json-editor.hooks.ts +21 -0
- package/src/json-editor/json-editor.libs.ts +34 -0
- package/src/json-editor/json-editor.tsx +425 -0
- package/src/json-editor/json-editor.types.ts +80 -0
- package/src/json-editor/json-editor.view.tsx +110 -0
- package/src/json-editor/json-text-area.tsx +7 -0
- package/src/kbd/index.ts +1 -0
- package/src/kbd/kbd.tsx +39 -0
- package/src/label/index.ts +1 -0
- package/src/label/label.tsx +28 -0
- package/src/menubar/index.ts +1 -0
- package/src/menubar/menubar.tsx +213 -0
- package/src/navigation-menu/index.ts +1 -0
- package/src/navigation-menu/navigation-menu.tsx +152 -0
- package/src/pagination/index.ts +2 -0
- package/src/pagination/pagination.tsx +191 -0
- package/src/pagination/pagination.types.ts +17 -0
- package/src/popover/index.ts +1 -0
- package/src/popover/popover.tsx +35 -0
- package/src/preview-panel/index.ts +3 -0
- package/src/preview-panel/preview-panel-dialog.tsx +99 -0
- package/src/preview-panel/preview-panel.tsx +389 -0
- package/src/preview-panel/preview-panel.types.ts +49 -0
- package/src/progress/index.ts +1 -0
- package/src/progress/progress.tsx +32 -0
- package/src/radio-group/index.ts +1 -0
- package/src/radio-group/radio-group.tsx +92 -0
- package/src/resizable/index.ts +1 -0
- package/src/resizable/resizable.tsx +52 -0
- package/src/scroll-area/index.ts +1 -0
- package/src/scroll-area/scroll-area.tsx +30 -0
- package/src/select/index.ts +1 -0
- package/src/select/select.tsx +138 -0
- package/src/separator/index.ts +1 -0
- package/src/separator/separator.tsx +28 -0
- package/src/sheet/index.ts +2 -0
- package/src/sheet/sheet.constants.tsx +20 -0
- package/src/sheet/sheet.tsx +92 -0
- package/src/sidebar/index.ts +4 -0
- package/src/sidebar/sidebar.constants.ts +30 -0
- package/src/sidebar/sidebar.hooks.ts +13 -0
- package/src/sidebar/sidebar.tsx +676 -0
- package/src/sidebar/sidebar.types.ts +28 -0
- package/src/skeleton/index.ts +1 -0
- package/src/skeleton/skeleton.tsx +22 -0
- package/src/slider/index.ts +1 -0
- package/src/slider/slider.tsx +57 -0
- package/src/sonner/index.ts +4 -0
- package/src/sonner/sonner.chunks.tsx +80 -0
- package/src/sonner/sonner.libs.ts +13 -0
- package/src/sonner/sonner.tsx +31 -0
- package/src/sonner/sonner.types.ts +9 -0
- package/src/switch/index.ts +1 -0
- package/src/switch/switch.tsx +63 -0
- package/src/table/index.ts +1 -0
- package/src/table/table.tsx +95 -0
- package/src/tabs/index.ts +1 -0
- package/src/tabs/tabs.tsx +151 -0
- package/src/textarea/index.ts +1 -0
- package/src/textarea/textarea.tsx +24 -0
- package/src/toggle/index.ts +2 -0
- package/src/toggle/toggle.constants.ts +22 -0
- package/src/toggle/toggle.tsx +24 -0
- package/src/toggle-group/index.ts +1 -0
- package/src/toggle-group/toggle-group.tsx +69 -0
- package/src/tooltip/index.ts +1 -0
- package/src/tooltip/tooltip.tsx +32 -0
- package/src/upload/index.ts +1 -0
- package/src/upload/upload.constants.tsx +19 -0
- package/src/upload/upload.libs.ts +97 -0
- package/src/upload/upload.tsx +340 -0
- package/src/upload/upload.types.ts +44 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@gentleduck/libs/cn'
|
|
4
|
+
import * as ToggleGroupPrimitive from '@gentleduck/primitives/toggle-group'
|
|
5
|
+
import type { VariantProps } from '@gentleduck/variants'
|
|
6
|
+
import * as React from 'react'
|
|
7
|
+
import { toggleVariants } from '../toggle/toggle.constants'
|
|
8
|
+
|
|
9
|
+
interface ToggleGroupContextProps extends VariantProps<typeof toggleVariants> {}
|
|
10
|
+
|
|
11
|
+
const ToggleGroupContext = React.createContext<ToggleGroupContextProps>({
|
|
12
|
+
size: 'default',
|
|
13
|
+
variant: 'default',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
type ToggleGroupElement = React.ComponentRef<typeof ToggleGroupPrimitive.Root>
|
|
17
|
+
type ToggleGroupProps = React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
|
18
|
+
VariantProps<typeof toggleVariants>
|
|
19
|
+
|
|
20
|
+
const ToggleGroup: React.ForwardRefExoticComponent<ToggleGroupProps & React.RefAttributes<ToggleGroupElement>> =
|
|
21
|
+
React.forwardRef<ToggleGroupElement, ToggleGroupProps>(
|
|
22
|
+
({ className, variant = 'default', size = 'default', children, ...props }, ref) => {
|
|
23
|
+
return (
|
|
24
|
+
<ToggleGroupContext.Provider value={{ size, variant }}>
|
|
25
|
+
<ToggleGroupPrimitive.Root
|
|
26
|
+
className={cn(
|
|
27
|
+
'isolate flex items-center justify-center rounded-md *:first:rounded-s-md *:last:rounded-e-md',
|
|
28
|
+
variant === 'outline' &&
|
|
29
|
+
'[&>*:first-child]:border-e-0 [&>*:not(:first-child):not(:last-child)]:border-e-0',
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
ref={ref}
|
|
33
|
+
data-slot="toggle-group"
|
|
34
|
+
{...props}>
|
|
35
|
+
{children}
|
|
36
|
+
</ToggleGroupPrimitive.Root>
|
|
37
|
+
</ToggleGroupContext.Provider>
|
|
38
|
+
)
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
ToggleGroup.displayName = 'ToggleGroup'
|
|
42
|
+
|
|
43
|
+
type ToggleGroupItemElement = React.ComponentRef<typeof ToggleGroupPrimitive.Item>
|
|
44
|
+
type ToggleGroupItemProps = React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
|
45
|
+
VariantProps<typeof toggleVariants>
|
|
46
|
+
const ToggleGroupItem: React.ForwardRefExoticComponent<
|
|
47
|
+
ToggleGroupItemProps & React.RefAttributes<ToggleGroupItemElement>
|
|
48
|
+
> = React.forwardRef<ToggleGroupItemElement, ToggleGroupItemProps>(
|
|
49
|
+
({ className, variant, size, children, ...props }, ref) => {
|
|
50
|
+
const context = React.useContext(ToggleGroupContext)
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<ToggleGroupPrimitive.Item
|
|
54
|
+
className={cn(
|
|
55
|
+
toggleVariants({ variant: variant || context.variant, size: size || context.size }),
|
|
56
|
+
'relative rounded-none focus-visible:z-10 focus-visible:ring-offset-0',
|
|
57
|
+
className,
|
|
58
|
+
)}
|
|
59
|
+
ref={ref}
|
|
60
|
+
data-slot="toggle-group-item"
|
|
61
|
+
{...props}>
|
|
62
|
+
{children}
|
|
63
|
+
</ToggleGroupPrimitive.Item>
|
|
64
|
+
)
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
ToggleGroupItem.displayName = 'ToggleGroupItem'
|
|
68
|
+
|
|
69
|
+
export { ToggleGroup, ToggleGroupItem }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './tooltip'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@gentleduck/libs/cn'
|
|
4
|
+
import * as TooltipPrimitive from '@gentleduck/primitives/tooltip'
|
|
5
|
+
import * as React from 'react'
|
|
6
|
+
|
|
7
|
+
const TooltipProvider = TooltipPrimitive.Provider
|
|
8
|
+
|
|
9
|
+
const Tooltip = TooltipPrimitive.Root
|
|
10
|
+
|
|
11
|
+
const TooltipTrigger = TooltipPrimitive.Trigger
|
|
12
|
+
|
|
13
|
+
const TooltipContent = React.forwardRef<
|
|
14
|
+
React.ComponentRef<typeof TooltipPrimitive.Content>,
|
|
15
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
16
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
17
|
+
<TooltipPrimitive.Portal>
|
|
18
|
+
<TooltipPrimitive.Content
|
|
19
|
+
ref={ref}
|
|
20
|
+
sideOffset={sideOffset}
|
|
21
|
+
className={cn(
|
|
22
|
+
'fade-in-0 zoom-in-95 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 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 z-50 origin-(--gentleduck-tooltip-content-transform-origin) animate-in overflow-hidden rounded-md border bg-background px-3 py-1.5 text-base text-foreground data-[state=closed]:animate-out',
|
|
23
|
+
'transition-all transition-discrete duration-[200ms,150ms] ease-(--duck-motion-ease)',
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
</TooltipPrimitive.Portal>
|
|
29
|
+
))
|
|
30
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
|
31
|
+
|
|
32
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './upload'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { File, FileAudio, FileImage, FileText, FileVideo } from 'lucide-react'
|
|
2
|
+
|
|
3
|
+
export enum FileTypeEnum {
|
|
4
|
+
Audio = 'audio',
|
|
5
|
+
Text = 'text',
|
|
6
|
+
Image = 'image',
|
|
7
|
+
Video = 'video',
|
|
8
|
+
Pdf = 'pdf',
|
|
9
|
+
Unknown = 'unknown',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const FILE_TYPE_ICONS: Record<FileTypeEnum, React.JSX.Element> = {
|
|
13
|
+
[FileTypeEnum.Audio]: <FileAudio aria-hidden="true" className="w-8 h-8" />,
|
|
14
|
+
[FileTypeEnum.Text]: <FileText aria-hidden="true" className="w-8 h-8" />,
|
|
15
|
+
[FileTypeEnum.Image]: <FileImage aria-hidden="true" className="w-8 h-8" />,
|
|
16
|
+
[FileTypeEnum.Video]: <FileVideo aria-hidden="true" className="w-8 h-8" />,
|
|
17
|
+
[FileTypeEnum.Pdf]: <FileText aria-hidden="true" className="w-8 h-8" />,
|
|
18
|
+
[FileTypeEnum.Unknown]: <File aria-hidden="true" className="w-8 h-8" />,
|
|
19
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { toast } from 'sonner'
|
|
2
|
+
import { uuidv7 } from 'uuidv7'
|
|
3
|
+
import { FileTypeEnum } from './upload.constants'
|
|
4
|
+
import { FileType } from './upload.types'
|
|
5
|
+
|
|
6
|
+
export const getFileType = (type: string): string => {
|
|
7
|
+
if (!type) return FileTypeEnum.Unknown
|
|
8
|
+
if (type.startsWith('audio/')) return FileTypeEnum.Audio
|
|
9
|
+
if (type.startsWith('text/')) return FileTypeEnum.Text
|
|
10
|
+
if (type.startsWith('image/')) return FileTypeEnum.Image
|
|
11
|
+
if (type.startsWith('video/')) return FileTypeEnum.Video
|
|
12
|
+
if (type.startsWith('application/pdf')) return FileTypeEnum.Pdf
|
|
13
|
+
return FileTypeEnum.Unknown
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const getAttachmentsToState = ({
|
|
17
|
+
e,
|
|
18
|
+
setAttachmentsState,
|
|
19
|
+
}: {
|
|
20
|
+
e: React.ChangeEvent<HTMLInputElement>
|
|
21
|
+
setAttachmentsState: React.Dispatch<React.SetStateAction<FileType[]>>
|
|
22
|
+
}) => {
|
|
23
|
+
const files = e.currentTarget.files
|
|
24
|
+
|
|
25
|
+
if (!files) return toast.error('Please select a file')
|
|
26
|
+
|
|
27
|
+
const newAttachments: FileType[] = []
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < files.length; i++) {
|
|
30
|
+
const file = files[i]
|
|
31
|
+
|
|
32
|
+
if (!file) return
|
|
33
|
+
// if (file !== undefined && file.size > 10 * 1024 * 1024) {
|
|
34
|
+
// toast.error(
|
|
35
|
+
// `File has exceeded the max size: ${file.name.slice(0, 15)}...`,
|
|
36
|
+
// )
|
|
37
|
+
// return
|
|
38
|
+
// // continue // Skip this file and continue with the next
|
|
39
|
+
// }
|
|
40
|
+
|
|
41
|
+
const attachment: FileType = {
|
|
42
|
+
file: file,
|
|
43
|
+
id: uuidv7(),
|
|
44
|
+
name: file.name,
|
|
45
|
+
size: file.size,
|
|
46
|
+
type: file.type,
|
|
47
|
+
url: null,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
newAttachments.push(attachment)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setAttachmentsState((prev) => [...prev, ...newAttachments])
|
|
54
|
+
e.currentTarget.value = ''
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const downloadAttachment = async ({ attachment }: { attachment: FileType }) => {
|
|
58
|
+
if (attachment.file) {
|
|
59
|
+
const file: Blob = attachment.file as Blob
|
|
60
|
+
return download(file, attachment.name ?? 'image.jpg')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (attachment.url) {
|
|
64
|
+
const file = await fetchBlob({
|
|
65
|
+
url: 'https://cdn.dribbble.com/userupload/15140814/file/original-22eddfd50ce84be4acb8bbbd50cf7840.jpg?resize=1600x1200',
|
|
66
|
+
})
|
|
67
|
+
return download(file ?? new Blob([]), attachment.name ?? 'image.jpg')
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function download(blob: Blob, name: string) {
|
|
72
|
+
const url = URL.createObjectURL(blob)
|
|
73
|
+
const a = document.createElement('a')
|
|
74
|
+
|
|
75
|
+
a.href = url
|
|
76
|
+
a.download = new Date().getTime() + '_' + name
|
|
77
|
+
document.body.appendChild(a)
|
|
78
|
+
a.click()
|
|
79
|
+
document.body.removeChild(a)
|
|
80
|
+
URL.revokeObjectURL(url)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const fetchBlob = async ({ url }: { url: string }): Promise<Blob | null> => {
|
|
84
|
+
try {
|
|
85
|
+
const response = await fetch(url)
|
|
86
|
+
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(`Failed to fetch audio: ${response.statusText} (status: ${response.status})`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const blob = await response.blob()
|
|
92
|
+
return blob
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('Error fetching audio:', error)
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// 'use client'
|
|
2
|
+
//
|
|
3
|
+
// import { cn } from '@gentleduck/libs/cn'
|
|
4
|
+
// import { filesize } from 'filesize'
|
|
5
|
+
// import { Download, Ellipsis, Trash, Upload as UploadIcon } from 'lucide-react'
|
|
6
|
+
// import { X } from 'lucide-react'
|
|
7
|
+
// import React from 'react'
|
|
8
|
+
// import { uuidv7 } from 'uuidv7'
|
|
9
|
+
// import { AlertDialogSheet } from '../alert-dialog'
|
|
10
|
+
// import { Button, buttonVariants } from '../button'
|
|
11
|
+
// import { ContextMenu, ContextMenuTrigger } from '../context-menu'
|
|
12
|
+
// import { Input } from '../input'
|
|
13
|
+
// import { ScrollArea } from '../scroll-area'
|
|
14
|
+
// import { FILE_TYPE_ICONS } from './upload.constants'
|
|
15
|
+
// import { downloadAttachment, getAttachmentsToState } from './upload.libs'
|
|
16
|
+
// import { getFileType } from './upload.libs'
|
|
17
|
+
// import {
|
|
18
|
+
// FileType,
|
|
19
|
+
// UploadContentProps,
|
|
20
|
+
// UploadContextType,
|
|
21
|
+
// UploadInputProps,
|
|
22
|
+
// UploadItemProps,
|
|
23
|
+
// UploadProps,
|
|
24
|
+
// UploadTriggerProps,
|
|
25
|
+
// UploadtItemRemoveProps,
|
|
26
|
+
// } from './upload.types'
|
|
27
|
+
// import { Avatar } from '../avatar'
|
|
28
|
+
// import { DropdownMenuView } from '../dropdown-menu'
|
|
29
|
+
//
|
|
30
|
+
// const UploadContext = React.createContext<UploadContextType<FileType> | null>(null)
|
|
31
|
+
//
|
|
32
|
+
// export const useUploadContext = (): UploadContextType<FileType> => {
|
|
33
|
+
// const context = React.useContext(UploadContext)
|
|
34
|
+
// if (!context) {
|
|
35
|
+
// throw new Error('useUploadContext must be used within an UploadProvider')
|
|
36
|
+
// }
|
|
37
|
+
// return context
|
|
38
|
+
// }
|
|
39
|
+
//
|
|
40
|
+
// export const UploadProvider = ({ children }: { children: React.ReactNode }): JSX.Element => {
|
|
41
|
+
// const [attachments, setAttachments] = React.useState<FileType[]>([])
|
|
42
|
+
// const [attachmentsState, setAttachmentsState] = React.useState<FileType[]>([])
|
|
43
|
+
//
|
|
44
|
+
// return (
|
|
45
|
+
// <UploadContext.Provider
|
|
46
|
+
// value={{
|
|
47
|
+
// attachments,
|
|
48
|
+
// setAttachments,
|
|
49
|
+
// attachmentsState,
|
|
50
|
+
// setAttachmentsState,
|
|
51
|
+
// }}>
|
|
52
|
+
// {children}
|
|
53
|
+
// </UploadContext.Provider>
|
|
54
|
+
// )
|
|
55
|
+
// }
|
|
56
|
+
//
|
|
57
|
+
// export const Upload = ({ children, trigger, content }: UploadProps): JSX.Element => {
|
|
58
|
+
// const { setAttachments, attachmentsState, setAttachmentsState } = useUploadContext()
|
|
59
|
+
// return (
|
|
60
|
+
// <>
|
|
61
|
+
// {children ? (
|
|
62
|
+
// children
|
|
63
|
+
// ) : (
|
|
64
|
+
// <AlertDialogSheet
|
|
65
|
+
// header={{
|
|
66
|
+
// head: 'Upload',
|
|
67
|
+
// description: 'upload your attahment here and submit.',
|
|
68
|
+
// }}
|
|
69
|
+
// actions={{
|
|
70
|
+
// continue: () => {
|
|
71
|
+
// setAttachments([])
|
|
72
|
+
// setAttachmentsState([])
|
|
73
|
+
// },
|
|
74
|
+
// }}
|
|
75
|
+
// footer={{
|
|
76
|
+
// submit: {
|
|
77
|
+
// children: (
|
|
78
|
+
// <Button
|
|
79
|
+
// disabled={attachmentsState.length === 0}
|
|
80
|
+
// className="px-6"
|
|
81
|
+
// onClick={() => {
|
|
82
|
+
// setAttachments((prev) => [...prev, ...attachmentsState])
|
|
83
|
+
// setAttachmentsState([])
|
|
84
|
+
// }}>
|
|
85
|
+
// Submit
|
|
86
|
+
// </Button>
|
|
87
|
+
// ),
|
|
88
|
+
// },
|
|
89
|
+
// cancel: {
|
|
90
|
+
// children: (
|
|
91
|
+
// <Button variant="outline" className="px-6">
|
|
92
|
+
// Cancel
|
|
93
|
+
// </Button>
|
|
94
|
+
// ),
|
|
95
|
+
// },
|
|
96
|
+
// }}
|
|
97
|
+
// state={attachmentsState.length > 0}
|
|
98
|
+
// trigger={{ children: trigger }}
|
|
99
|
+
// content={{ children: content }}
|
|
100
|
+
// />
|
|
101
|
+
// )}
|
|
102
|
+
// </>
|
|
103
|
+
// )
|
|
104
|
+
// }
|
|
105
|
+
//
|
|
106
|
+
// export const UploadTrigger = ({ className, children, ref, ...props }: UploadTriggerProps) => (
|
|
107
|
+
// <div className={cn(className)} ref={ref} {...props}>
|
|
108
|
+
// {children}
|
|
109
|
+
// </div>
|
|
110
|
+
// )
|
|
111
|
+
//
|
|
112
|
+
// export const UploadInput = ({ className, children, ref, ...props }: UploadInputProps) => {
|
|
113
|
+
// const { setAttachmentsState } = useUploadContext()
|
|
114
|
+
//
|
|
115
|
+
// return (
|
|
116
|
+
// <div className={cn(className)} ref={ref} {...props}>
|
|
117
|
+
// <ContextMenu>
|
|
118
|
+
// <ContextMenuTrigger className="relative flex flex-col items-center justify-center w-full h-64 rounded-md border border-dashed border-border text-sm leading-5 transition-colors duration-100 ease-in-out hover:bg-muted/10">
|
|
119
|
+
// <div className="grid place-items-center gap-4">
|
|
120
|
+
// <UploadIcon className="size-[30px]" />
|
|
121
|
+
// <span>Click or Drag to Upload</span>
|
|
122
|
+
// </div>
|
|
123
|
+
// <Input
|
|
124
|
+
// placeholder="Filter files..."
|
|
125
|
+
// type="file"
|
|
126
|
+
// className="absolute w-full h-full opacity-0 cursor-pointer"
|
|
127
|
+
// multiple={true}
|
|
128
|
+
// onChange={(e) => getAttachmentsToState({ e, setAttachmentsState })}
|
|
129
|
+
// />
|
|
130
|
+
// </ContextMenuTrigger>
|
|
131
|
+
// </ContextMenu>
|
|
132
|
+
// <p className="mt-2 text-muted-foreground text-[.9rem]">supports all types of files.</p>
|
|
133
|
+
// </div>
|
|
134
|
+
// )
|
|
135
|
+
// }
|
|
136
|
+
//
|
|
137
|
+
// export const UploadContent = ({ className, children, ref, ...props }: UploadContentProps) => {
|
|
138
|
+
// const { attachmentsState, setAttachmentsState } = useUploadContext()
|
|
139
|
+
//
|
|
140
|
+
// return (
|
|
141
|
+
// <ScrollArea className={cn('flex flex-col gap-2 max-h-[39ch] md:max-h-[43ch]', className)} ref={ref} {...props}>
|
|
142
|
+
// {children}
|
|
143
|
+
// <div className="flex flex-col gap-2">
|
|
144
|
+
// {attachmentsState.map((attachment) => {
|
|
145
|
+
// return (
|
|
146
|
+
// <UploadItem key={attachment.id} attachment={attachment}>
|
|
147
|
+
// <UploadtItemRemove
|
|
148
|
+
// className="absolute top-1/2 -translate-y-1/2 right-2"
|
|
149
|
+
// onClick={() => {
|
|
150
|
+
// setAttachmentsState((prev) => prev.filter((item) => item.id !== attachment.id))
|
|
151
|
+
// }}
|
|
152
|
+
// />
|
|
153
|
+
// </UploadItem>
|
|
154
|
+
// )
|
|
155
|
+
// })}
|
|
156
|
+
// </div>
|
|
157
|
+
// </ScrollArea>
|
|
158
|
+
// )
|
|
159
|
+
// }
|
|
160
|
+
//
|
|
161
|
+
// export const UploadItem = React.forwardRef<HTMLDivElement, UploadItemProps>(
|
|
162
|
+
// ({ attachment, children, className, ...props }, ref) => {
|
|
163
|
+
// const fileType = getFileType(attachment.file.type)
|
|
164
|
+
//
|
|
165
|
+
// return (
|
|
166
|
+
// <div
|
|
167
|
+
// className={cn('relative flex items-center gap-4 bg-secondary/20 rounded-md p-2', className)}
|
|
168
|
+
// ref={ref}
|
|
169
|
+
// {...props}>
|
|
170
|
+
// <div className="flex items-center gap-4">
|
|
171
|
+
// <div className="relative">{FILE_TYPE_ICONS[fileType]}</div>
|
|
172
|
+
// <div className="grid items-start">
|
|
173
|
+
// <h3 className="inline-block text-[.9rem] truncate max-w-[200px]">{attachment.name || 'Empty File'}</h3>
|
|
174
|
+
// <p className="inline-block truncate text-semibold text-[.8rem] max-w-[300px]">
|
|
175
|
+
// {filesize(attachment.file ? +attachment.file.size : 0, {
|
|
176
|
+
// round: 0,
|
|
177
|
+
// })}
|
|
178
|
+
// </p>
|
|
179
|
+
// </div>
|
|
180
|
+
// </div>
|
|
181
|
+
// {children}
|
|
182
|
+
// </div>
|
|
183
|
+
// )
|
|
184
|
+
// },
|
|
185
|
+
// )
|
|
186
|
+
//
|
|
187
|
+
// /**
|
|
188
|
+
// * UploadtItemRemove component represents a remove button for an uploaded file item.
|
|
189
|
+
// * It is typically used for removing a file from the upload list.
|
|
190
|
+
// *
|
|
191
|
+
// * @param {Object} props - The properties passed to the component.
|
|
192
|
+
// * @param {string} [props.className] - Optional additional class names.
|
|
193
|
+
// * @param {React.Ref} ref - The ref forwarded to the underlying div element.
|
|
194
|
+
// *
|
|
195
|
+
// * @returns {React.Element} The rendered component.
|
|
196
|
+
// */
|
|
197
|
+
// export const UploadtItemRemove = React.forwardRef<HTMLDivElement, UploadtItemRemoveProps>(
|
|
198
|
+
// ({ className, ...props }, ref) => {
|
|
199
|
+
// return (
|
|
200
|
+
// <div
|
|
201
|
+
// className={cn(
|
|
202
|
+
// 'size-4 rounded-md focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 cursor-pointer',
|
|
203
|
+
// className,
|
|
204
|
+
// )}
|
|
205
|
+
// ref={ref}
|
|
206
|
+
// {...props}>
|
|
207
|
+
// <X className="w-4 h-4" />
|
|
208
|
+
// </div>
|
|
209
|
+
// )
|
|
210
|
+
// },
|
|
211
|
+
// )
|
|
212
|
+
//
|
|
213
|
+
// export const UploadItemsPreview = () => {
|
|
214
|
+
// const { attachments } = useUploadContext()
|
|
215
|
+
//
|
|
216
|
+
// return attachments.length > 0 ? (
|
|
217
|
+
// <div className="grid grid-cols-6 justify-start items-start place-content-start gap-2 w-full border border-border min-h-[400px] p-4 rounded-lg">
|
|
218
|
+
// {attachments.map((attachment) => {
|
|
219
|
+
// const fileType = getFileType(attachment.file.type)
|
|
220
|
+
//
|
|
221
|
+
// // // If the file is a File object, generate a URL for preview
|
|
222
|
+
// // const src =
|
|
223
|
+
// // typeof attachment.file === 'string'
|
|
224
|
+
// // ? attachment.file
|
|
225
|
+
// // : URL.createObjectURL(attachment.file as Blob)
|
|
226
|
+
//
|
|
227
|
+
// return (
|
|
228
|
+
// <div
|
|
229
|
+
// className={cn(
|
|
230
|
+
// 'relative bg-secondary/20 rounded-md overflow-hidden w-full flex flex-col place-content-center gap-4 h-[100px] border border-border',
|
|
231
|
+
// )}>
|
|
232
|
+
// <div>
|
|
233
|
+
// <div className="relative [&_svg]:size-12 [&_svg]:mx-auto w-full">{FILE_TYPE_ICONS[fileType]}</div>
|
|
234
|
+
// </div>
|
|
235
|
+
// <DropdownMenuView
|
|
236
|
+
// trigger={{
|
|
237
|
+
// icon: <Ellipsis className="h-4 w-4 rounded-sm" />,
|
|
238
|
+
// variant: 'outline',
|
|
239
|
+
// size: 'icon',
|
|
240
|
+
// className: 'h-4 w-6 absolute bottom-2 right-2',
|
|
241
|
+
// }}
|
|
242
|
+
// content={{
|
|
243
|
+
// options: {
|
|
244
|
+
// itemType: 'label',
|
|
245
|
+
// optionsData: [
|
|
246
|
+
// {
|
|
247
|
+
// actionType: 'item',
|
|
248
|
+
// children: 'Download',
|
|
249
|
+
// icon: <Download className="h-4 w-4 rounded-sm" />,
|
|
250
|
+
// onClick: () => {
|
|
251
|
+
// downloadAttachment({ attachment: attachment! })
|
|
252
|
+
// },
|
|
253
|
+
// },
|
|
254
|
+
// {
|
|
255
|
+
// actionType: 'item',
|
|
256
|
+
// children: 'Delete',
|
|
257
|
+
// className: 'text-red-500 bg-red-500/10',
|
|
258
|
+
// icon: <Trash className="h-4 w-4 rounded-sm" />,
|
|
259
|
+
//
|
|
260
|
+
// onClick: () => {},
|
|
261
|
+
// },
|
|
262
|
+
// ],
|
|
263
|
+
// },
|
|
264
|
+
// }}
|
|
265
|
+
// />
|
|
266
|
+
// </div>
|
|
267
|
+
// )
|
|
268
|
+
// })}
|
|
269
|
+
// </div>
|
|
270
|
+
// ) : (
|
|
271
|
+
// <div className="flex items-center w-full border border-border min-h-[400px] p-4 rounded-lg">
|
|
272
|
+
// <p className="text-center w-full">There's no attachments yet uploaded.</p>
|
|
273
|
+
// </div>
|
|
274
|
+
// )
|
|
275
|
+
// }
|
|
276
|
+
//
|
|
277
|
+
// export const UploadProfile = () => {
|
|
278
|
+
// const { attachments, setAttachments } = useUploadContext() ?? {}
|
|
279
|
+
// const src =
|
|
280
|
+
// attachments.length > 0
|
|
281
|
+
// ? typeof attachments?.[0]?.file === 'string'
|
|
282
|
+
// ? attachments[0].file
|
|
283
|
+
// : URL.createObjectURL(attachments?.[0]?.file as Blob)
|
|
284
|
+
// : null
|
|
285
|
+
//
|
|
286
|
+
// return (
|
|
287
|
+
// <Button className="relative cursor-pointer w-16 h-16 rounded-full" variant={'outline'}>
|
|
288
|
+
// <Input
|
|
289
|
+
// placeholder="Filter files..."
|
|
290
|
+
// type="file"
|
|
291
|
+
// className="absolute w-full h-full opacity-0 cursor-pointer"
|
|
292
|
+
// multiple={false}
|
|
293
|
+
// onChange={(e) => {
|
|
294
|
+
// const file = e.currentTarget.files?.[0]
|
|
295
|
+
// if (file) {
|
|
296
|
+
// setAttachments([
|
|
297
|
+
// {
|
|
298
|
+
// id: uuidv7(),
|
|
299
|
+
// file: file,
|
|
300
|
+
// name: file.name,
|
|
301
|
+
// url: null,
|
|
302
|
+
// type: file.type,
|
|
303
|
+
// size: file.size,
|
|
304
|
+
// },
|
|
305
|
+
// ])
|
|
306
|
+
// }
|
|
307
|
+
// }}
|
|
308
|
+
// />
|
|
309
|
+
// <Avatar className="w-16 h-16 pointer-events-none" src={src ?? '/avatars/02.png'} />
|
|
310
|
+
// <span
|
|
311
|
+
// className={cn(
|
|
312
|
+
// buttonVariants({ variant: 'outline' }),
|
|
313
|
+
// 'absolute rounded-full p-2 -bottom-1 -left-1 hover:bg-background h-fit pointer-events-none',
|
|
314
|
+
// )}>
|
|
315
|
+
// <UploadIcon className="!size-3" />
|
|
316
|
+
// </span>
|
|
317
|
+
// </Button>
|
|
318
|
+
// )
|
|
319
|
+
// }
|
|
320
|
+
//
|
|
321
|
+
// export const UploadDirectButton = () => {
|
|
322
|
+
// const { setAttachments } = useUploadContext() ?? {}
|
|
323
|
+
//
|
|
324
|
+
// return (
|
|
325
|
+
// <Button className="relative" variant={'outline'} size={'sm'} icon={<UploadIcon />}>
|
|
326
|
+
// <Input
|
|
327
|
+
// placeholder="Filter files..."
|
|
328
|
+
// type="file"
|
|
329
|
+
// className="absolute w-full h-full opacity-0 cursor-pointer"
|
|
330
|
+
// multiple={true}
|
|
331
|
+
// onChange={(e) => getAttachmentsToState({ e, setAttachmentsState: setAttachments })}
|
|
332
|
+
// />
|
|
333
|
+
// Upload file
|
|
334
|
+
// </Button>
|
|
335
|
+
// )
|
|
336
|
+
// }
|
|
337
|
+
|
|
338
|
+
import { type Direction, useDirection } from '@gentleduck/primitives/direction'
|
|
339
|
+
|
|
340
|
+
export const useUploadDirection = (dir?: 'ltr' | 'rtl') => useDirection(dir as Direction)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Button } from '../button'
|
|
2
|
+
import { ScrollArea } from '../scroll-area'
|
|
3
|
+
|
|
4
|
+
export type FileType = {
|
|
5
|
+
id: string
|
|
6
|
+
file: File
|
|
7
|
+
name: string
|
|
8
|
+
url: string | null
|
|
9
|
+
type: string
|
|
10
|
+
size: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type UploadRenameAttachmentButtonProps = {
|
|
14
|
+
attachment: FileType[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UploadContextType<T extends Record<string, any>> {
|
|
18
|
+
attachments: FileType[]
|
|
19
|
+
setAttachments: React.Dispatch<React.SetStateAction<FileType[]>>
|
|
20
|
+
attachmentsState: T[]
|
|
21
|
+
setAttachmentsState: React.Dispatch<React.SetStateAction<T[]>>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UploadInputProps extends React.HTMLProps<HTMLDivElement> {}
|
|
25
|
+
|
|
26
|
+
export interface UploadItemProps extends React.HTMLProps<HTMLDivElement> {
|
|
27
|
+
attachment: FileType
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface UploadProps extends Omit<React.HTMLProps<HTMLDivElement>, 'content'> {
|
|
31
|
+
trigger: React.ReactNode
|
|
32
|
+
content: React.ReactNode
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface UploadTriggerProps extends React.HTMLProps<HTMLDivElement> {}
|
|
36
|
+
|
|
37
|
+
export interface UploadtItemRemoveProps extends React.HTMLProps<HTMLDivElement> {}
|
|
38
|
+
|
|
39
|
+
export interface UploadContentProps extends React.ComponentPropsWithRef<typeof ScrollArea> {}
|
|
40
|
+
|
|
41
|
+
export interface StateWithExtraFeatures<T extends Record<string, any>> {
|
|
42
|
+
data: T | null
|
|
43
|
+
state: 'pending' | 'success' | 'error'
|
|
44
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": ".",
|
|
5
|
+
"incremental": false,
|
|
6
|
+
"isolatedModules": true,
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"lib": ["ES2022", "dom", "dom.iterable"],
|
|
9
|
+
"module": "preserve",
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"paths": {
|
|
13
|
+
"~/*": ["./*"]
|
|
14
|
+
},
|
|
15
|
+
"plugins": [
|
|
16
|
+
{
|
|
17
|
+
"name": "next"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"rootDir": "./"
|
|
21
|
+
},
|
|
22
|
+
"exclude": ["node_modules", "dist", "./src/_old", "**/*.tsbuildinfo", "tsconfig.tsbuildinfo"],
|
|
23
|
+
"extends": "@gentleduck/typescript-config/base.json",
|
|
24
|
+
"include": ["./**/*.ts", "./**/*.tsx"]
|
|
25
|
+
}
|