@carefully-built/cli 0.1.1 → 0.1.2

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 (213) hide show
  1. package/README.md +101 -80
  2. package/dist/index.mjs +8 -5
  3. package/dist/index.mjs.map +1 -1
  4. package/package.json +3 -3
  5. package/registry/ui/avatar/manifest.json +33 -0
  6. package/registry/ui/avatar/primitives/avatar.tsx +64 -0
  7. package/registry/ui/avatar/utils/cn.ts +6 -0
  8. package/registry/ui/button/manifest.json +24 -5
  9. package/registry/ui/button/utils/cn.ts +6 -0
  10. package/registry/ui/calendar/manifest.json +35 -0
  11. package/registry/ui/calendar/primitives/button.tsx +89 -0
  12. package/registry/ui/calendar/primitives/calendar.tsx +68 -0
  13. package/registry/ui/calendar/utils/cn.ts +6 -0
  14. package/registry/ui/card/manifest.json +36 -0
  15. package/registry/ui/card/primitives/card.tsx +80 -0
  16. package/registry/ui/card/utils/cn.ts +6 -0
  17. package/registry/ui/chip/manifest.json +36 -0
  18. package/registry/ui/chip/primitives/chip-utils.ts +10 -0
  19. package/registry/ui/chip/primitives/chip.tsx +74 -0
  20. package/registry/ui/chip/utils/cn.ts +6 -0
  21. package/registry/ui/chip-utils/manifest.json +33 -0
  22. package/registry/ui/chip-utils/primitives/chip-utils.ts +10 -0
  23. package/registry/ui/chip-utils/utils/cn.ts +6 -0
  24. package/registry/ui/date-display/manifest.json +33 -0
  25. package/registry/ui/date-display/utils/cn.ts +6 -0
  26. package/registry/ui/date-display/utils/date-display.ts +61 -0
  27. package/registry/ui/dialog/manifest.json +43 -0
  28. package/registry/ui/dialog/primitives/button.tsx +89 -0
  29. package/registry/ui/dialog/primitives/dialog.tsx +147 -0
  30. package/registry/ui/dialog/utils/cn.ts +6 -0
  31. package/registry/ui/display-date/manifest.json +36 -0
  32. package/registry/ui/display-date/primitives/display-date.tsx +20 -0
  33. package/registry/ui/display-date/utils/cn.ts +6 -0
  34. package/registry/ui/display-date/utils/date-display.ts +61 -0
  35. package/registry/ui/drawer/manifest.json +37 -0
  36. package/registry/ui/drawer/primitives/drawer.tsx +99 -0
  37. package/registry/ui/drawer/utils/cn.ts +6 -0
  38. package/registry/ui/dropdown-menu/manifest.json +37 -0
  39. package/registry/ui/dropdown-menu/primitives/dropdown-menu.tsx +140 -0
  40. package/registry/ui/dropdown-menu/utils/cn.ts +6 -0
  41. package/registry/ui/empty-state/empty-state/collection-empty-state.ts +29 -0
  42. package/registry/ui/empty-state/empty-state/empty-state-card.tsx +72 -0
  43. package/registry/ui/empty-state/empty-state/index.ts +8 -0
  44. package/registry/ui/empty-state/empty-state/initial-empty-state.tsx +36 -0
  45. package/registry/ui/empty-state/empty-state/no-results-state.tsx +20 -0
  46. package/registry/ui/empty-state/manifest.json +63 -0
  47. package/registry/ui/empty-state/primitives/button.tsx +89 -0
  48. package/registry/ui/empty-state/primitives/card.tsx +80 -0
  49. package/registry/ui/empty-state/utils/cn.ts +6 -0
  50. package/registry/ui/error-page/error-page/error-code.tsx +16 -0
  51. package/registry/ui/error-page/error-page/error-page-content.ts +75 -0
  52. package/registry/ui/error-page/error-page/index.ts +19 -0
  53. package/registry/ui/error-page/error-page/posthog-error-capture.ts +83 -0
  54. package/registry/ui/error-page/error-page/saas-error-page.tsx +146 -0
  55. package/registry/ui/error-page/manifest.json +64 -0
  56. package/registry/ui/error-page/primitives/button.tsx +89 -0
  57. package/registry/ui/error-page/utils/cn.ts +6 -0
  58. package/registry/ui/field-detail-row/manifest.json +32 -0
  59. package/registry/ui/field-detail-row/primitives/field-detail-row.tsx +28 -0
  60. package/registry/ui/field-detail-row/utils/cn.ts +6 -0
  61. package/registry/ui/file-dropzone/manifest.json +35 -0
  62. package/registry/ui/file-dropzone/primitives/button.tsx +89 -0
  63. package/registry/ui/file-dropzone/primitives/file-dropzone.tsx +236 -0
  64. package/registry/ui/file-dropzone/utils/cn.ts +6 -0
  65. package/registry/ui/help-info-button/manifest.json +72 -0
  66. package/registry/ui/help-info-button/overlays/responsive-sheet.footer.tsx +88 -0
  67. package/registry/ui/help-info-button/overlays/responsive-sheet.layouts.tsx +207 -0
  68. package/registry/ui/help-info-button/overlays/responsive-sheet.shortcuts.ts +103 -0
  69. package/registry/ui/help-info-button/overlays/responsive-sheet.tsx +132 -0
  70. package/registry/ui/help-info-button/primitives/button.tsx +89 -0
  71. package/registry/ui/help-info-button/primitives/drawer.tsx +99 -0
  72. package/registry/ui/help-info-button/primitives/help-info-button.tsx +63 -0
  73. package/registry/ui/help-info-button/primitives/keyboard-shortcut-hint.tsx +40 -0
  74. package/registry/ui/help-info-button/primitives/sheet.tsx +103 -0
  75. package/registry/ui/help-info-button/primitives/tooltip.tsx +57 -0
  76. package/registry/ui/help-info-button/utils/cn.ts +6 -0
  77. package/registry/ui/help-info-button/utils/use-media-query.ts +28 -0
  78. package/registry/ui/input/manifest.json +31 -0
  79. package/registry/ui/input/primitives/input.tsx +19 -0
  80. package/registry/ui/input/utils/cn.ts +6 -0
  81. package/registry/ui/keyboard-shortcut-hint/manifest.json +32 -0
  82. package/registry/ui/keyboard-shortcut-hint/primitives/keyboard-shortcut-hint.tsx +40 -0
  83. package/registry/ui/keyboard-shortcut-hint/utils/cn.ts +6 -0
  84. package/registry/ui/label/manifest.json +31 -0
  85. package/registry/ui/label/primitives/label.tsx +21 -0
  86. package/registry/ui/label/utils/cn.ts +6 -0
  87. package/registry/ui/pagination/manifest.json +36 -0
  88. package/registry/ui/pagination/primitives/button.tsx +89 -0
  89. package/registry/ui/pagination/primitives/pagination.tsx +143 -0
  90. package/registry/ui/pagination/utils/cn.ts +6 -0
  91. package/registry/ui/popover/manifest.json +33 -0
  92. package/registry/ui/popover/primitives/popover.tsx +46 -0
  93. package/registry/ui/popover/utils/cn.ts +6 -0
  94. package/registry/ui/responsive-sheet/manifest.json +66 -0
  95. package/registry/ui/responsive-sheet/overlays/responsive-sheet.footer.tsx +88 -0
  96. package/registry/ui/responsive-sheet/overlays/responsive-sheet.layouts.tsx +207 -0
  97. package/registry/ui/responsive-sheet/overlays/responsive-sheet.shortcuts.ts +103 -0
  98. package/registry/ui/responsive-sheet/overlays/responsive-sheet.tsx +132 -0
  99. package/registry/ui/responsive-sheet/primitives/button.tsx +89 -0
  100. package/registry/ui/responsive-sheet/primitives/drawer.tsx +99 -0
  101. package/registry/ui/responsive-sheet/primitives/keyboard-shortcut-hint.tsx +40 -0
  102. package/registry/ui/responsive-sheet/primitives/sheet.tsx +103 -0
  103. package/registry/ui/responsive-sheet/utils/cn.ts +6 -0
  104. package/registry/ui/responsive-sheet/utils/use-media-query.ts +28 -0
  105. package/registry/ui/responsive-sheet.footer/manifest.json +40 -0
  106. package/registry/ui/responsive-sheet.footer/overlays/responsive-sheet.footer.tsx +88 -0
  107. package/registry/ui/responsive-sheet.footer/primitives/button.tsx +89 -0
  108. package/registry/ui/responsive-sheet.footer/primitives/keyboard-shortcut-hint.tsx +40 -0
  109. package/registry/ui/responsive-sheet.footer/utils/cn.ts +6 -0
  110. package/registry/ui/responsive-sheet.shortcuts/manifest.json +34 -0
  111. package/registry/ui/responsive-sheet.shortcuts/overlays/responsive-sheet.shortcuts.ts +103 -0
  112. package/registry/ui/responsive-sheet.shortcuts/utils/cn.ts +6 -0
  113. package/registry/ui/scroll-fade-area/manifest.json +31 -0
  114. package/registry/ui/scroll-fade-area/primitives/scroll-fade-area.tsx +295 -0
  115. package/registry/ui/scroll-fade-area/utils/cn.ts +6 -0
  116. package/registry/ui/search/manifest.json +35 -0
  117. package/registry/ui/search/utils/cn.ts +6 -0
  118. package/registry/ui/search/utils/search.ts +227 -0
  119. package/registry/ui/searchable-select/manifest.json +48 -0
  120. package/registry/ui/searchable-select/primitives/input.tsx +19 -0
  121. package/registry/ui/searchable-select/search/searchable-select-position.ts +95 -0
  122. package/registry/ui/searchable-select/search/searchable-select.tsx +431 -0
  123. package/registry/ui/searchable-select/utils/cn.ts +6 -0
  124. package/registry/ui/searchable-select/utils/search.ts +227 -0
  125. package/registry/ui/searchable-select-position/manifest.json +32 -0
  126. package/registry/ui/searchable-select-position/search/searchable-select-position.ts +95 -0
  127. package/registry/ui/searchable-select-position/utils/cn.ts +6 -0
  128. package/registry/ui/segmented-toggle/manifest.json +41 -0
  129. package/registry/ui/segmented-toggle/primitives/scroll-fade-area.tsx +295 -0
  130. package/registry/ui/segmented-toggle/primitives/segmented-toggle.tsx +106 -0
  131. package/registry/ui/segmented-toggle/primitives/tabs.tsx +97 -0
  132. package/registry/ui/segmented-toggle/utils/cn.ts +6 -0
  133. package/registry/ui/select/manifest.json +37 -0
  134. package/registry/ui/select/primitives/select.tsx +142 -0
  135. package/registry/ui/select/utils/cn.ts +6 -0
  136. package/registry/ui/sheet/manifest.json +39 -0
  137. package/registry/ui/sheet/primitives/button.tsx +89 -0
  138. package/registry/ui/sheet/primitives/sheet.tsx +103 -0
  139. package/registry/ui/sheet/utils/cn.ts +6 -0
  140. package/registry/ui/skeleton/manifest.json +31 -0
  141. package/registry/ui/skeleton/primitives/skeleton.tsx +13 -0
  142. package/registry/ui/skeleton/utils/cn.ts +6 -0
  143. package/registry/ui/smart-table/manifest.json +115 -0
  144. package/registry/ui/smart-table/primitives/button.tsx +89 -0
  145. package/registry/ui/smart-table/primitives/card.tsx +80 -0
  146. package/registry/ui/smart-table/primitives/display-date.tsx +20 -0
  147. package/registry/ui/smart-table/primitives/pagination.tsx +143 -0
  148. package/registry/ui/smart-table/primitives/skeleton.tsx +13 -0
  149. package/registry/ui/smart-table/primitives/table.tsx +92 -0
  150. package/registry/ui/smart-table/primitives/tooltip.tsx +57 -0
  151. package/registry/ui/smart-table/smart-table/DesktopView.tsx +343 -0
  152. package/registry/ui/smart-table/smart-table/MobileView.tsx +170 -0
  153. package/registry/ui/smart-table/smart-table/SmartTable.tsx +85 -0
  154. package/registry/ui/smart-table/smart-table/SmartTableActions.tsx +71 -0
  155. package/registry/ui/smart-table/smart-table/TruncatedContent.tsx +147 -0
  156. package/registry/ui/smart-table/smart-table/index.ts +15 -0
  157. package/registry/ui/smart-table/smart-table/sorting.ts +148 -0
  158. package/registry/ui/smart-table/smart-table/truncated-content.utils.ts +22 -0
  159. package/registry/ui/smart-table/smart-table/types.ts +95 -0
  160. package/registry/ui/smart-table/smart-table/utils.ts +150 -0
  161. package/registry/ui/smart-table/utils/cn.ts +6 -0
  162. package/registry/ui/smart-table/utils/date-display.ts +61 -0
  163. package/registry/ui/smart-table/utils/use-media-query.ts +28 -0
  164. package/registry/ui/switch/manifest.json +31 -0
  165. package/registry/ui/switch/primitives/switch.tsx +31 -0
  166. package/registry/ui/switch/utils/cn.ts +6 -0
  167. package/registry/ui/table/manifest.json +38 -0
  168. package/registry/ui/table/primitives/table.tsx +92 -0
  169. package/registry/ui/table/utils/cn.ts +6 -0
  170. package/registry/ui/table-toolbar/manifest.json +93 -0
  171. package/registry/ui/table-toolbar/overlays/responsive-sheet.footer.tsx +88 -0
  172. package/registry/ui/table-toolbar/overlays/responsive-sheet.layouts.tsx +207 -0
  173. package/registry/ui/table-toolbar/overlays/responsive-sheet.shortcuts.ts +103 -0
  174. package/registry/ui/table-toolbar/overlays/responsive-sheet.tsx +132 -0
  175. package/registry/ui/table-toolbar/primitives/button.tsx +89 -0
  176. package/registry/ui/table-toolbar/primitives/drawer.tsx +99 -0
  177. package/registry/ui/table-toolbar/primitives/input.tsx +19 -0
  178. package/registry/ui/table-toolbar/primitives/keyboard-shortcut-hint.tsx +40 -0
  179. package/registry/ui/table-toolbar/primitives/sheet.tsx +103 -0
  180. package/registry/ui/table-toolbar/search/searchable-select-position.ts +95 -0
  181. package/registry/ui/table-toolbar/search/searchable-select.tsx +431 -0
  182. package/registry/ui/table-toolbar/table-toolbar/index.ts +9 -0
  183. package/registry/ui/table-toolbar/table-toolbar/table-toolbar.tsx +552 -0
  184. package/registry/ui/table-toolbar/utils/cn.ts +6 -0
  185. package/registry/ui/table-toolbar/utils/search.ts +227 -0
  186. package/registry/ui/table-toolbar/utils/use-media-query.ts +28 -0
  187. package/registry/ui/tabs/manifest.json +40 -0
  188. package/registry/ui/tabs/primitives/scroll-fade-area.tsx +295 -0
  189. package/registry/ui/tabs/primitives/tabs.tsx +97 -0
  190. package/registry/ui/tabs/utils/cn.ts +6 -0
  191. package/registry/ui/textarea/manifest.json +31 -0
  192. package/registry/ui/textarea/primitives/textarea.tsx +18 -0
  193. package/registry/ui/textarea/utils/cn.ts +6 -0
  194. package/registry/ui/tooltip/manifest.json +34 -0
  195. package/registry/ui/tooltip/primitives/tooltip.tsx +57 -0
  196. package/registry/ui/tooltip/utils/cn.ts +6 -0
  197. package/registry/ui/use-media-query/manifest.json +32 -0
  198. package/registry/ui/use-media-query/utils/cn.ts +6 -0
  199. package/registry/ui/use-media-query/utils/use-media-query.ts +28 -0
  200. package/registry/ui/user-picker/manifest.json +52 -0
  201. package/registry/ui/user-picker/primitives/avatar.tsx +64 -0
  202. package/registry/ui/user-picker/primitives/button.tsx +89 -0
  203. package/registry/ui/user-picker/primitives/input.tsx +19 -0
  204. package/registry/ui/user-picker/primitives/popover.tsx +46 -0
  205. package/registry/ui/user-picker/primitives/user-picker-utils.ts +113 -0
  206. package/registry/ui/user-picker/primitives/user-picker.tsx +226 -0
  207. package/registry/ui/user-picker/utils/cn.ts +6 -0
  208. package/registry/ui/user-picker-utils/manifest.json +38 -0
  209. package/registry/ui/user-picker-utils/primitives/user-picker-utils.ts +113 -0
  210. package/registry/ui/user-picker-utils/utils/cn.ts +6 -0
  211. package/assets/hero.png +0 -0
  212. package/registry/ui/button/cn.ts +0 -6
  213. /package/registry/ui/button/{button.tsx → primitives/button.tsx} +0 -0
@@ -0,0 +1,236 @@
1
+ 'use client';
2
+
3
+ import { Check, FileText, Upload, X } from 'lucide-react';
4
+ import { useRef, useState } from 'react';
5
+
6
+ import { Button } from '@/components/ui/button';
7
+ import { cn } from '@/lib/utils';
8
+
9
+ interface FileDropzoneProps {
10
+ readonly accept?: string;
11
+ readonly browseLabel?: string;
12
+ readonly browseButtonClassName?: string;
13
+ readonly className?: string;
14
+ readonly currentPreviewUrl?: string | null;
15
+ readonly disabled?: boolean;
16
+ readonly emptyIcon?: React.ReactNode;
17
+ readonly helperText?: string;
18
+ readonly footerText?: string | null;
19
+ readonly inputClassName?: string;
20
+ readonly multiple?: boolean;
21
+ readonly onFileSelect: (file: File) => void;
22
+ readonly onFilesSelect?: (files: File[]) => void;
23
+ readonly onRemove?: () => void;
24
+ readonly previewAlt: string;
25
+ readonly title?: string;
26
+ }
27
+
28
+ function formatFileSize(size: number): string {
29
+ if (size < 1024) {
30
+ return `${size} B`;
31
+ }
32
+
33
+ if (size < 1024 * 1024) {
34
+ return `${Math.round(size / 1024)} KB`;
35
+ }
36
+
37
+ return `${(size / (1024 * 1024)).toFixed(1)} MB`;
38
+ }
39
+
40
+ function getFileTypeLabel(file: File): string {
41
+ return file.type || file.name.split('.').pop()?.toUpperCase() || 'File';
42
+ }
43
+
44
+ export function FileDropzone({
45
+ accept,
46
+ browseLabel = 'Esplora',
47
+ browseButtonClassName,
48
+ className,
49
+ currentPreviewUrl = null,
50
+ disabled = false,
51
+ emptyIcon,
52
+ helperText,
53
+ footerText = 'Drag a file here or click the box to select one.',
54
+ inputClassName,
55
+ multiple = false,
56
+ onFileSelect,
57
+ onFilesSelect,
58
+ onRemove,
59
+ previewAlt,
60
+ title = 'Lascia qui o esplora file',
61
+ }: FileDropzoneProps): React.ReactElement {
62
+ const inputRef = useRef<HTMLInputElement>(null);
63
+ const [isDragging, setIsDragging] = useState(false);
64
+ const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
65
+ const hasSelectedFiles = selectedFiles.length > 0;
66
+
67
+ const openFilePicker = (): void => {
68
+ if (disabled) {
69
+ return;
70
+ }
71
+
72
+ inputRef.current?.click();
73
+ };
74
+
75
+ const handleFileSelection = (files: FileList | File[] | null | undefined): void => {
76
+ const selectedFiles = Array.from(files ?? []);
77
+ if (selectedFiles.length === 0 || disabled) {
78
+ return;
79
+ }
80
+
81
+ setSelectedFiles(selectedFiles);
82
+
83
+ if (multiple && onFilesSelect) {
84
+ onFilesSelect(selectedFiles);
85
+ return;
86
+ }
87
+
88
+ const firstFile = selectedFiles[0];
89
+ if (firstFile) {
90
+ onFileSelect(firstFile);
91
+ }
92
+ };
93
+
94
+ const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
95
+ handleFileSelection(event.target.files);
96
+ event.target.value = '';
97
+ };
98
+
99
+ const handleDrop = (event: React.DragEvent<HTMLDivElement>): void => {
100
+ event.preventDefault();
101
+ event.stopPropagation();
102
+ setIsDragging(false);
103
+ handleFileSelection(event.dataTransfer.files);
104
+ };
105
+
106
+ const handleRemove = (event: React.MouseEvent<HTMLButtonElement>): void => {
107
+ event.stopPropagation();
108
+ setSelectedFiles([]);
109
+ onRemove?.();
110
+ };
111
+
112
+ return (
113
+ <>
114
+ <input
115
+ ref={inputRef}
116
+ type="file"
117
+ accept={accept}
118
+ multiple={multiple}
119
+ className="hidden"
120
+ onChange={handleInputChange}
121
+ disabled={disabled}
122
+ />
123
+ <div
124
+ role="button"
125
+ tabIndex={disabled ? -1 : 0}
126
+ aria-disabled={disabled}
127
+ onClick={openFilePicker}
128
+ onKeyDown={(event): void => {
129
+ if (event.key === 'Enter' || event.key === ' ') {
130
+ event.preventDefault();
131
+ openFilePicker();
132
+ }
133
+ }}
134
+ onDragEnter={(event): void => {
135
+ event.preventDefault();
136
+ event.stopPropagation();
137
+ if (!disabled) {
138
+ setIsDragging(true);
139
+ }
140
+ }}
141
+ onDragOver={(event): void => {
142
+ event.preventDefault();
143
+ event.stopPropagation();
144
+ if (!disabled) {
145
+ setIsDragging(true);
146
+ }
147
+ }}
148
+ onDragLeave={(event): void => {
149
+ event.preventDefault();
150
+ event.stopPropagation();
151
+ setIsDragging(false);
152
+ }}
153
+ onDrop={handleDrop}
154
+ className={cn(
155
+ 'bg-muted/30 relative flex w-full cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed px-6 py-5 text-center transition-colors outline-none',
156
+ 'border-primary/60 hover:bg-muted/50 focus-visible:ring-primary/20 focus-visible:ring-2',
157
+ isDragging && 'bg-muted/60',
158
+ disabled && 'cursor-not-allowed opacity-60',
159
+ className,
160
+ )}
161
+ >
162
+ {currentPreviewUrl ? (
163
+ <div className="bg-background relative flex min-h-40 w-full items-center justify-center overflow-hidden rounded-md">
164
+ <img src={currentPreviewUrl} alt={previewAlt} className="size-full object-contain" />
165
+ {onRemove ? (
166
+ <Button
167
+ type="button"
168
+ variant="destructive"
169
+ size="icon-xs"
170
+ className="absolute top-2 right-2 z-10"
171
+ onClick={handleRemove}
172
+ disabled={disabled}
173
+ >
174
+ <X className="size-3.5" />
175
+ </Button>
176
+ ) : null}
177
+ {hasSelectedFiles ? (
178
+ <div className="bg-background/95 absolute right-2 bottom-2 left-2 rounded-md border px-3 py-2 text-left shadow-sm backdrop-blur">
179
+ <p className="truncate text-sm font-medium">{selectedFiles[0]?.name}</p>
180
+ {selectedFiles[0] ? (
181
+ <p className="text-muted-foreground text-xs">
182
+ {getFileTypeLabel(selectedFiles[0])} | {formatFileSize(selectedFiles[0].size)}
183
+ </p>
184
+ ) : null}
185
+ </div>
186
+ ) : null}
187
+ </div>
188
+ ) : (
189
+ <>
190
+ <div className="text-primary mb-2 flex items-center justify-center">
191
+ {emptyIcon ?? <Upload className="size-5" />}
192
+ </div>
193
+ <div className="space-y-0.5">
194
+ <p className="text-foreground text-base font-medium">{title}</p>
195
+ {helperText ? <p className="text-muted-foreground text-sm">{helperText}</p> : null}
196
+ </div>
197
+ <Button
198
+ type="button"
199
+ size="sm"
200
+ className={cn('mt-4 shadow-[0px_1px_1px_rgba(5,32,81,0.05)]', browseButtonClassName)}
201
+ onClick={(event): void => {
202
+ event.stopPropagation();
203
+ openFilePicker();
204
+ }}
205
+ disabled={disabled}
206
+ >
207
+ {browseLabel}
208
+ </Button>
209
+ {hasSelectedFiles ? (
210
+ <div className="mt-4 w-full space-y-2">
211
+ {selectedFiles.map((file) => (
212
+ <div
213
+ key={`${file.name}-${file.size}-${file.lastModified}`}
214
+ className="bg-background flex min-w-0 items-center gap-3 rounded-md border px-3 py-2 text-left"
215
+ >
216
+ <FileText className="text-muted-foreground size-4 shrink-0" />
217
+ <div className="min-w-0 flex-1">
218
+ <p className="truncate text-sm font-medium">{file.name}</p>
219
+ <p className="text-muted-foreground text-xs">
220
+ {getFileTypeLabel(file)} | {formatFileSize(file.size)}
221
+ </p>
222
+ </div>
223
+ <Check className="text-primary size-4 shrink-0" />
224
+ </div>
225
+ ))}
226
+ </div>
227
+ ) : null}
228
+ </>
229
+ )}
230
+ </div>
231
+ {footerText ? (
232
+ <p className={cn('text-muted-foreground mt-2 text-xs', inputClassName)}>{footerText}</p>
233
+ ) : null}
234
+ </>
235
+ );
236
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]): string {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "help-info-button",
3
+ "description": "Editable source registry entry for help-info-button.",
4
+ "importPath": "@carefully-built/ui",
5
+ "exports": [
6
+ "HelpInfoButton",
7
+ "HelpInfoButtonProps"
8
+ ],
9
+ "dependencies": [
10
+ "class-variance-authority",
11
+ "clsx",
12
+ "tailwind-merge"
13
+ ],
14
+ "peerDependencies": [
15
+ "react",
16
+ "react-dom",
17
+ "radix-ui",
18
+ "lucide-react",
19
+ "react-day-picker",
20
+ "vaul"
21
+ ],
22
+ "files": [
23
+ {
24
+ "source": "overlays/responsive-sheet.footer.tsx",
25
+ "target": "components/ui/responsive-sheet.footer.tsx"
26
+ },
27
+ {
28
+ "source": "overlays/responsive-sheet.layouts.tsx",
29
+ "target": "components/ui/responsive-sheet.layouts.tsx"
30
+ },
31
+ {
32
+ "source": "overlays/responsive-sheet.shortcuts.ts",
33
+ "target": "components/ui/responsive-sheet.shortcuts.ts"
34
+ },
35
+ {
36
+ "source": "overlays/responsive-sheet.tsx",
37
+ "target": "components/ui/responsive-sheet.tsx"
38
+ },
39
+ {
40
+ "source": "primitives/button.tsx",
41
+ "target": "components/ui/button.tsx"
42
+ },
43
+ {
44
+ "source": "primitives/drawer.tsx",
45
+ "target": "components/ui/drawer.tsx"
46
+ },
47
+ {
48
+ "source": "primitives/help-info-button.tsx",
49
+ "target": "components/ui/help-info-button.tsx"
50
+ },
51
+ {
52
+ "source": "primitives/keyboard-shortcut-hint.tsx",
53
+ "target": "components/ui/keyboard-shortcut-hint.tsx"
54
+ },
55
+ {
56
+ "source": "primitives/sheet.tsx",
57
+ "target": "components/ui/sheet.tsx"
58
+ },
59
+ {
60
+ "source": "primitives/tooltip.tsx",
61
+ "target": "components/ui/tooltip.tsx"
62
+ },
63
+ {
64
+ "source": "utils/cn.ts",
65
+ "target": "lib/utils.ts"
66
+ },
67
+ {
68
+ "source": "utils/use-media-query.ts",
69
+ "target": "components/ui/use-media-query.ts"
70
+ }
71
+ ]
72
+ }
@@ -0,0 +1,88 @@
1
+ 'use client';
2
+
3
+ import { CornerDownLeft } from 'lucide-react';
4
+
5
+ import type { ReactNode } from 'react';
6
+
7
+ import { Button } from '@/components/ui/button';
8
+ import { KeyboardKeycap, ShortcutModifierKeycap } from '@/components/ui/keyboard-shortcut-hint';
9
+
10
+ export function DesktopConfirmShortcutHint({
11
+ desktopModifierLabel,
12
+ }: {
13
+ readonly desktopModifierLabel: string;
14
+ }): React.ReactElement {
15
+ const keycapClassName = 'border-primary-foreground/20 bg-primary-foreground/10';
16
+
17
+ return (
18
+ <span className="text-primary-foreground/70 inline-flex items-center gap-1">
19
+ <ShortcutModifierKeycap modifierLabel={desktopModifierLabel} className={keycapClassName} />
20
+ <KeyboardKeycap className={keycapClassName}>
21
+ <CornerDownLeft className="size-[10px]" />
22
+ </KeyboardKeycap>
23
+ </span>
24
+ );
25
+ }
26
+
27
+ interface SheetActionFooterProps {
28
+ readonly footer?: ReactNode;
29
+ readonly onCancel?: () => void;
30
+ readonly cancelLabel: ReactNode;
31
+ readonly onConfirm?: () => void;
32
+ readonly confirmLabel: ReactNode;
33
+ readonly confirmDisabled: boolean;
34
+ readonly confirmLoading: boolean;
35
+ readonly desktopConfirmShortcutEnabled?: boolean;
36
+ readonly desktopModifierLabel?: string | null;
37
+ }
38
+
39
+ export function SheetActionFooter({
40
+ footer,
41
+ onCancel,
42
+ cancelLabel,
43
+ onConfirm,
44
+ confirmLabel,
45
+ confirmDisabled,
46
+ confirmLoading,
47
+ desktopConfirmShortcutEnabled = false,
48
+ desktopModifierLabel = null,
49
+ }: SheetActionFooterProps): React.ReactNode {
50
+ if (footer) return footer;
51
+
52
+ if (!onCancel && !onConfirm) return null;
53
+
54
+ const actionCount = Number(Boolean(onCancel)) + Number(Boolean(onConfirm));
55
+ const footerButtonClassName = actionCount === 1 ? 'w-full' : 'min-w-0 flex-1';
56
+
57
+ return (
58
+ <div className="flex w-full flex-nowrap items-center gap-2">
59
+ {onCancel ? (
60
+ <Button
61
+ type="button"
62
+ variant="outline"
63
+ onClick={onCancel}
64
+ className={footerButtonClassName}
65
+ >
66
+ {cancelLabel}
67
+ </Button>
68
+ ) : null}
69
+ {onConfirm ? (
70
+ <Button
71
+ type="button"
72
+ onClick={onConfirm}
73
+ disabled={confirmDisabled}
74
+ className={`${footerButtonClassName} relative`}
75
+ >
76
+ <span className="inline-flex w-full items-center justify-center">
77
+ <span>{confirmLoading ? 'Saving...' : confirmLabel}</span>
78
+ {desktopConfirmShortcutEnabled && desktopModifierLabel ? (
79
+ <span className="absolute top-1/2 right-2 -translate-y-1/2">
80
+ <DesktopConfirmShortcutHint desktopModifierLabel={desktopModifierLabel} />
81
+ </span>
82
+ ) : null}
83
+ </span>
84
+ </Button>
85
+ ) : null}
86
+ </div>
87
+ );
88
+ }
@@ -0,0 +1,207 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+
5
+ import type { SheetOutsideInteractionGuard } from '@/components/ui/responsive-sheet';
6
+ import type { ResponsiveSheetClassNames } from '@/components/ui/responsive-sheet';
7
+
8
+ import {
9
+ Drawer,
10
+ DrawerContent,
11
+ DrawerDescription,
12
+ DrawerHeader,
13
+ DrawerTitle,
14
+ } from '@/components/ui/drawer';
15
+ import {
16
+ Sheet,
17
+ SheetContent,
18
+ SheetDescription,
19
+ SheetHeader,
20
+ SheetTitle,
21
+ } from '@/components/ui/sheet';
22
+ import { cn } from '@/lib/utils';
23
+
24
+ interface SheetDescriptionBlockProps {
25
+ readonly title: ReactNode;
26
+ readonly description?: ReactNode;
27
+ readonly classes?: ResponsiveSheetClassNames;
28
+ }
29
+
30
+ function SheetDescriptionBlock({
31
+ title,
32
+ description,
33
+ classes,
34
+ }: SheetDescriptionBlockProps): React.ReactElement {
35
+ return (
36
+ <>
37
+ <SheetTitle className={classes?.title}>{title}</SheetTitle>
38
+ {description ? (
39
+ <SheetDescription className={classes?.description}>{description}</SheetDescription>
40
+ ) : null}
41
+ </>
42
+ );
43
+ }
44
+
45
+ interface SharedSheetLayoutProps {
46
+ readonly open: boolean;
47
+ readonly onOpenChange: (open: boolean) => void;
48
+ readonly modal: boolean;
49
+ readonly outsideInteractionGuard?: SheetOutsideInteractionGuard;
50
+ readonly title: ReactNode;
51
+ readonly description?: ReactNode;
52
+ readonly children: ReactNode;
53
+ readonly footer: ReactNode;
54
+ readonly mobileDrawerContentClassName?: string;
55
+ readonly contentClassName?: string;
56
+ readonly footerClassName?: string;
57
+ readonly classes?: ResponsiveSheetClassNames;
58
+ }
59
+
60
+ function shouldPreventOutsideInteraction(
61
+ target: EventTarget | null,
62
+ guard?: SheetOutsideInteractionGuard,
63
+ ): boolean {
64
+ const element =
65
+ target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
66
+
67
+ if (!element) {
68
+ return false;
69
+ }
70
+
71
+ if (element.closest('[data-searchable-select-content]')) {
72
+ return true;
73
+ }
74
+
75
+ return guard?.selectors.some((selector) => element.closest(selector)) ?? false;
76
+ }
77
+
78
+ type MobileSheetLayoutProps = SharedSheetLayoutProps;
79
+
80
+ export function MobileSheetLayout({
81
+ open,
82
+ onOpenChange,
83
+ modal,
84
+ outsideInteractionGuard,
85
+ title,
86
+ description,
87
+ children,
88
+ footer,
89
+ mobileDrawerContentClassName,
90
+ contentClassName,
91
+ footerClassName,
92
+ classes,
93
+ }: MobileSheetLayoutProps): React.ReactElement {
94
+ return (
95
+ <Drawer open={open} onOpenChange={onOpenChange} modal={modal}>
96
+ <DrawerContent
97
+ aria-describedby={description ? undefined : 'responsive-sheet-description-empty'}
98
+ className={cn(
99
+ 'px-4 pb-[calc(env(safe-area-inset-bottom)+20px)]',
100
+ mobileDrawerContentClassName,
101
+ classes?.mobileContent,
102
+ )}
103
+ onInteractOutside={(event) => {
104
+ if (shouldPreventOutsideInteraction(event.target, outsideInteractionGuard)) {
105
+ event.preventDefault();
106
+ }
107
+ }}
108
+ onPointerDownOutside={(event) => {
109
+ if (shouldPreventOutsideInteraction(event.target, outsideInteractionGuard)) {
110
+ event.preventDefault();
111
+ }
112
+ }}
113
+ >
114
+ <DrawerHeader className={cn('px-0 pb-4', classes?.header)}>
115
+ <DrawerTitle className={classes?.title}>{title}</DrawerTitle>
116
+ {description ? (
117
+ <DrawerDescription className={classes?.description}>{description}</DrawerDescription>
118
+ ) : (
119
+ <DrawerDescription id="responsive-sheet-description-empty" className="sr-only">
120
+ Dialog
121
+ </DrawerDescription>
122
+ )}
123
+ </DrawerHeader>
124
+ <div className="flex min-h-0 flex-1 flex-col gap-4">
125
+ <div
126
+ className={cn(
127
+ '-mx-1 flex-1 overflow-y-auto px-1 pb-3 [scrollbar-gutter:stable]',
128
+ contentClassName,
129
+ classes?.body,
130
+ )}
131
+ >
132
+ {children}
133
+ </div>
134
+ {footer ? (
135
+ <div className={cn('shrink-0 border-t pt-4 pb-3', footerClassName, classes?.footer)}>
136
+ {footer}
137
+ </div>
138
+ ) : null}
139
+ </div>
140
+ </DrawerContent>
141
+ </Drawer>
142
+ );
143
+ }
144
+
145
+ interface DesktopSheetLayoutProps extends SharedSheetLayoutProps {
146
+ readonly width: number;
147
+ }
148
+
149
+ export function DesktopSheetLayout({
150
+ open,
151
+ onOpenChange,
152
+ modal,
153
+ outsideInteractionGuard,
154
+ title,
155
+ description,
156
+ children,
157
+ footer,
158
+ width,
159
+ contentClassName,
160
+ footerClassName,
161
+ classes,
162
+ }: DesktopSheetLayoutProps): React.ReactElement {
163
+ return (
164
+ <Sheet open={open} onOpenChange={onOpenChange} modal={modal}>
165
+ <SheetContent
166
+ aria-describedby={description ? undefined : 'responsive-sheet-description-empty'}
167
+ style={{ width: `${String(width)}px`, maxWidth: '85vw' }}
168
+ className={cn('flex flex-col gap-0 p-0', classes?.desktopContent)}
169
+ onInteractOutside={(event) => {
170
+ if (shouldPreventOutsideInteraction(event.target, outsideInteractionGuard)) {
171
+ event.preventDefault();
172
+ }
173
+ }}
174
+ onPointerDownOutside={(event) => {
175
+ if (shouldPreventOutsideInteraction(event.target, outsideInteractionGuard)) {
176
+ event.preventDefault();
177
+ }
178
+ }}
179
+ >
180
+ <SheetHeader className={cn('border-b px-4 py-4', classes?.header)}>
181
+ <SheetDescriptionBlock title={title} description={description} classes={classes} />
182
+ </SheetHeader>
183
+ {description ? null : (
184
+ <SheetDescription id="responsive-sheet-description-empty" className="sr-only">
185
+ Dialog
186
+ </SheetDescription>
187
+ )}
188
+ <div className="flex min-h-0 flex-1 flex-col">
189
+ <div
190
+ className={cn(
191
+ 'flex-1 overflow-x-visible overflow-y-auto px-4 pt-4 pb-6 [scrollbar-gutter:stable]',
192
+ contentClassName,
193
+ classes?.body,
194
+ )}
195
+ >
196
+ {children}
197
+ </div>
198
+ {footer ? (
199
+ <div className={cn('border-t px-4 py-4', footerClassName, classes?.footer)}>
200
+ {footer}
201
+ </div>
202
+ ) : null}
203
+ </div>
204
+ </SheetContent>
205
+ </Sheet>
206
+ );
207
+ }