@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.
Files changed (175) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/index.css +3 -0
  3. package/package.json +59 -0
  4. package/src/_old/_table/index.ts +5 -0
  5. package/src/_old/_table/table-advanced.constants.tsx +24 -0
  6. package/src/_old/_table/table-advanced.tsx +311 -0
  7. package/src/_old/_table/table-advanced.types.ts +272 -0
  8. package/src/_old/_table/table.constants.ts +2 -0
  9. package/src/_old/_table/table.hook.tsx +115 -0
  10. package/src/_old/_table/table.lib.ts +85 -0
  11. package/src/_old/_table/table.tsx +916 -0
  12. package/src/_old/_table/table.types.ts +118 -0
  13. package/src/_old/_table/todo.md +11 -0
  14. package/src/_old/_upload/index.ts +9 -0
  15. package/src/_old/_upload/todo.md +38 -0
  16. package/src/_old/_upload/upload-advanced-chunks.tsx +1624 -0
  17. package/src/_old/_upload/upload-advanced.tsx +507 -0
  18. package/src/_old/_upload/upload-sonner.tsx +58 -0
  19. package/src/_old/_upload/upload.assets.tsx +239 -0
  20. package/src/_old/_upload/upload.constants.tsx +75 -0
  21. package/src/_old/_upload/upload.dto.ts +19 -0
  22. package/src/_old/_upload/upload.lib.tsx +630 -0
  23. package/src/_old/_upload/upload.tsx +491 -0
  24. package/src/_old/_upload/upload.types.ts +436 -0
  25. package/src/accordion/accordion.tsx +247 -0
  26. package/src/accordion/index.ts +1 -0
  27. package/src/alert/alert.constants.ts +17 -0
  28. package/src/alert/alert.tsx +52 -0
  29. package/src/alert/index.ts +2 -0
  30. package/src/alert-dialog/alert-dialog.tsx +107 -0
  31. package/src/alert-dialog/index.ts +1 -0
  32. package/src/aspect-ratio/aspect-ratio.tsx +33 -0
  33. package/src/aspect-ratio/index.ts +1 -0
  34. package/src/audio/audio-record.tsx +776 -0
  35. package/src/audio/audio-visualizer.tsx +377 -0
  36. package/src/audio/audio.libs.ts +5 -0
  37. package/src/audio/audio.types.ts +50 -0
  38. package/src/audio/index.ts +2 -0
  39. package/src/avatar/avatar.tsx +78 -0
  40. package/src/avatar/index.ts +1 -0
  41. package/src/badge/badge.constants.ts +38 -0
  42. package/src/badge/badge.tsx +19 -0
  43. package/src/badge/index.ts +2 -0
  44. package/src/breadcrumb/breadcrumb.tsx +119 -0
  45. package/src/breadcrumb/index.ts +1 -0
  46. package/src/button/button.constants.ts +44 -0
  47. package/src/button/button.tsx +79 -0
  48. package/src/button/button.types.ts +38 -0
  49. package/src/button/index.ts +3 -0
  50. package/src/button-group/button-group.constants.ts +26 -0
  51. package/src/button-group/button-group.tsx +65 -0
  52. package/src/button-group/index.ts +2 -0
  53. package/src/calendar/calendar.tsx +191 -0
  54. package/src/calendar/index.ts +1 -0
  55. package/src/card/card.tsx +81 -0
  56. package/src/card/index.ts +1 -0
  57. package/src/carousel/carousel.tsx +211 -0
  58. package/src/carousel/carousel.types.ts +23 -0
  59. package/src/carousel/index.ts +2 -0
  60. package/src/chart/chart.libs.ts +27 -0
  61. package/src/chart/chart.tsx +260 -0
  62. package/src/chart/chart.types.ts +38 -0
  63. package/src/chart/index.ts +3 -0
  64. package/src/checkbox/checkbox.tsx +144 -0
  65. package/src/checkbox/checkbox.types.ts +24 -0
  66. package/src/checkbox/index.ts +2 -0
  67. package/src/collapsible/collapsible.tsx +151 -0
  68. package/src/collapsible/index.ts +1 -0
  69. package/src/combobox/combobox.tsx +132 -0
  70. package/src/combobox/index.ts +1 -0
  71. package/src/command/command.tsx +192 -0
  72. package/src/command/command.types.ts +11 -0
  73. package/src/command/index.ts +2 -0
  74. package/src/context-menu/context-menu.tsx +178 -0
  75. package/src/context-menu/index.ts +1 -0
  76. package/src/dialog/dialog-responsive.tsx +137 -0
  77. package/src/dialog/dialog.tsx +97 -0
  78. package/src/dialog/index.ts +2 -0
  79. package/src/direction/direction.tsx +13 -0
  80. package/src/direction/index.ts +1 -0
  81. package/src/drawer/drawer.tsx +185 -0
  82. package/src/drawer/index.ts +1 -0
  83. package/src/dropdown-menu/dropdown-menu.tsx +181 -0
  84. package/src/dropdown-menu/index.ts +1 -0
  85. package/src/empty/empty.constants.ts +15 -0
  86. package/src/empty/empty.tsx +73 -0
  87. package/src/empty/index.ts +2 -0
  88. package/src/field/field.constants.ts +22 -0
  89. package/src/field/field.tsx +203 -0
  90. package/src/field/index.ts +2 -0
  91. package/src/hover-card/hover-card.tsx +79 -0
  92. package/src/hover-card/index.ts +1 -0
  93. package/src/input/index.ts +1 -0
  94. package/src/input/input.tsx +45 -0
  95. package/src/input-group/index.ts +1 -0
  96. package/src/input-group/input-group.tsx +170 -0
  97. package/src/input-otp/index.ts +1 -0
  98. package/src/input-otp/input-otp.tsx +66 -0
  99. package/src/item/index.ts +2 -0
  100. package/src/item/item.constants.ts +22 -0
  101. package/src/item/item.tsx +185 -0
  102. package/src/json-editor/index.ts +4 -0
  103. package/src/json-editor/json-editor.hooks.ts +21 -0
  104. package/src/json-editor/json-editor.libs.ts +34 -0
  105. package/src/json-editor/json-editor.tsx +425 -0
  106. package/src/json-editor/json-editor.types.ts +80 -0
  107. package/src/json-editor/json-editor.view.tsx +110 -0
  108. package/src/json-editor/json-text-area.tsx +7 -0
  109. package/src/kbd/index.ts +1 -0
  110. package/src/kbd/kbd.tsx +39 -0
  111. package/src/label/index.ts +1 -0
  112. package/src/label/label.tsx +28 -0
  113. package/src/menubar/index.ts +1 -0
  114. package/src/menubar/menubar.tsx +213 -0
  115. package/src/navigation-menu/index.ts +1 -0
  116. package/src/navigation-menu/navigation-menu.tsx +152 -0
  117. package/src/pagination/index.ts +2 -0
  118. package/src/pagination/pagination.tsx +191 -0
  119. package/src/pagination/pagination.types.ts +17 -0
  120. package/src/popover/index.ts +1 -0
  121. package/src/popover/popover.tsx +35 -0
  122. package/src/preview-panel/index.ts +3 -0
  123. package/src/preview-panel/preview-panel-dialog.tsx +99 -0
  124. package/src/preview-panel/preview-panel.tsx +389 -0
  125. package/src/preview-panel/preview-panel.types.ts +49 -0
  126. package/src/progress/index.ts +1 -0
  127. package/src/progress/progress.tsx +32 -0
  128. package/src/radio-group/index.ts +1 -0
  129. package/src/radio-group/radio-group.tsx +92 -0
  130. package/src/resizable/index.ts +1 -0
  131. package/src/resizable/resizable.tsx +52 -0
  132. package/src/scroll-area/index.ts +1 -0
  133. package/src/scroll-area/scroll-area.tsx +30 -0
  134. package/src/select/index.ts +1 -0
  135. package/src/select/select.tsx +138 -0
  136. package/src/separator/index.ts +1 -0
  137. package/src/separator/separator.tsx +28 -0
  138. package/src/sheet/index.ts +2 -0
  139. package/src/sheet/sheet.constants.tsx +20 -0
  140. package/src/sheet/sheet.tsx +92 -0
  141. package/src/sidebar/index.ts +4 -0
  142. package/src/sidebar/sidebar.constants.ts +30 -0
  143. package/src/sidebar/sidebar.hooks.ts +13 -0
  144. package/src/sidebar/sidebar.tsx +676 -0
  145. package/src/sidebar/sidebar.types.ts +28 -0
  146. package/src/skeleton/index.ts +1 -0
  147. package/src/skeleton/skeleton.tsx +22 -0
  148. package/src/slider/index.ts +1 -0
  149. package/src/slider/slider.tsx +57 -0
  150. package/src/sonner/index.ts +4 -0
  151. package/src/sonner/sonner.chunks.tsx +80 -0
  152. package/src/sonner/sonner.libs.ts +13 -0
  153. package/src/sonner/sonner.tsx +31 -0
  154. package/src/sonner/sonner.types.ts +9 -0
  155. package/src/switch/index.ts +1 -0
  156. package/src/switch/switch.tsx +63 -0
  157. package/src/table/index.ts +1 -0
  158. package/src/table/table.tsx +95 -0
  159. package/src/tabs/index.ts +1 -0
  160. package/src/tabs/tabs.tsx +151 -0
  161. package/src/textarea/index.ts +1 -0
  162. package/src/textarea/textarea.tsx +24 -0
  163. package/src/toggle/index.ts +2 -0
  164. package/src/toggle/toggle.constants.ts +22 -0
  165. package/src/toggle/toggle.tsx +24 -0
  166. package/src/toggle-group/index.ts +1 -0
  167. package/src/toggle-group/toggle-group.tsx +69 -0
  168. package/src/tooltip/index.ts +1 -0
  169. package/src/tooltip/tooltip.tsx +32 -0
  170. package/src/upload/index.ts +1 -0
  171. package/src/upload/upload.constants.tsx +19 -0
  172. package/src/upload/upload.libs.ts +97 -0
  173. package/src/upload/upload.tsx +340 -0
  174. package/src/upload/upload.types.ts +44 -0
  175. 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
+ }