@mostrom/app-shell 0.1.0

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 (142) hide show
  1. package/.claude/ralph-loop.local.md +9 -0
  2. package/README.md +172 -0
  3. package/bin/init.js +269 -0
  4. package/bun.lock +401 -0
  5. package/components.json +28 -0
  6. package/package.json +74 -0
  7. package/scripts/publish-npm.sh +202 -0
  8. package/src/AppShell.tsx +847 -0
  9. package/src/components/PageHeader.tsx +160 -0
  10. package/src/components/data-table/README.md +447 -0
  11. package/src/components/data-table/data-table-preferences.tsx +184 -0
  12. package/src/components/data-table/data-table-toolbar.tsx +118 -0
  13. package/src/components/data-table/data-table.tsx +37 -0
  14. package/src/components/data-table/index.ts +32 -0
  15. package/src/components/global-header/AllServicesButton.tsx +127 -0
  16. package/src/components/global-header/CategoriesButton.tsx +120 -0
  17. package/src/components/global-header/GlobalHeader.tsx +59 -0
  18. package/src/components/global-header/GlobalHeaderSearch.tsx +57 -0
  19. package/src/components/global-header/HeaderUtilities.tsx +243 -0
  20. package/src/components/global-header/ServicesMenu.tsx +246 -0
  21. package/src/components/layout/AppBreadcrumb.tsx +70 -0
  22. package/src/components/layout/AppFlashbar.tsx +95 -0
  23. package/src/components/layout/AppLayout.tsx +271 -0
  24. package/src/components/layout/AppNavigation.tsx +313 -0
  25. package/src/components/layout/AppSidebar.tsx +229 -0
  26. package/src/components/patterns/index.ts +14 -0
  27. package/src/components/patterns/p-alert-5.tsx +19 -0
  28. package/src/components/patterns/p-autocomplete-5.tsx +89 -0
  29. package/src/components/patterns/p-breadcrumb-1.tsx +28 -0
  30. package/src/components/patterns/p-button-42.tsx +37 -0
  31. package/src/components/patterns/p-button-51.tsx +14 -0
  32. package/src/components/patterns/p-button-6.tsx +5 -0
  33. package/src/components/patterns/p-calendar-1.tsx +18 -0
  34. package/src/components/patterns/p-card-1.tsx +33 -0
  35. package/src/components/patterns/p-card-2.tsx +26 -0
  36. package/src/components/patterns/p-card-5.tsx +31 -0
  37. package/src/components/patterns/p-collapsible-7.tsx +121 -0
  38. package/src/components/patterns/p-command-6.tsx +113 -0
  39. package/src/components/patterns/p-dialog-1.tsx +56 -0
  40. package/src/components/patterns/p-dropdown-menu-1.tsx +38 -0
  41. package/src/components/patterns/p-dropdown-menu-11.tsx +122 -0
  42. package/src/components/patterns/p-dropdown-menu-14.tsx +165 -0
  43. package/src/components/patterns/p-dropdown-menu-9.tsx +108 -0
  44. package/src/components/patterns/p-empty-2.tsx +34 -0
  45. package/src/components/patterns/p-file-upload-1.tsx +72 -0
  46. package/src/components/patterns/p-filters-1.tsx +666 -0
  47. package/src/components/patterns/p-frame-2.tsx +26 -0
  48. package/src/components/patterns/p-tabs-2.tsx +129 -0
  49. package/src/components/reui/alert.tsx +92 -0
  50. package/src/components/reui/autocomplete.tsx +343 -0
  51. package/src/components/reui/badge.tsx +87 -0
  52. package/src/components/reui/data-grid/data-grid-column-filter.tsx +165 -0
  53. package/src/components/reui/data-grid/data-grid-column-header.tsx +339 -0
  54. package/src/components/reui/data-grid/data-grid-column-visibility.tsx +55 -0
  55. package/src/components/reui/data-grid/data-grid-pagination.tsx +224 -0
  56. package/src/components/reui/data-grid/data-grid-table-dnd-rows.tsx +260 -0
  57. package/src/components/reui/data-grid/data-grid-table-dnd.tsx +253 -0
  58. package/src/components/reui/data-grid/data-grid-table.tsx +639 -0
  59. package/src/components/reui/data-grid/data-grid.tsx +209 -0
  60. package/src/components/reui/date-selector.tsx +1330 -0
  61. package/src/components/reui/filters.tsx +1869 -0
  62. package/src/components/reui/frame.tsx +134 -0
  63. package/src/components/reui/index.ts +17 -0
  64. package/src/components/reui/timeline.tsx +219 -0
  65. package/src/components/search/Autocomplete.tsx +183 -0
  66. package/src/components/search/AutocompleteClient.tsx +293 -0
  67. package/src/components/search/GlobalSearch.tsx +187 -0
  68. package/src/components/section-drawer/deal-drawer-content.tsx +891 -0
  69. package/src/components/section-drawer/index.ts +19 -0
  70. package/src/components/section-drawer/section-drawer.css +665 -0
  71. package/src/components/section-drawer/section-drawer.tsx +467 -0
  72. package/src/components/sectioned-list-board/README.md +78 -0
  73. package/src/components/sectioned-list-board/board-card-content.tsx +340 -0
  74. package/src/components/sectioned-list-board/date-range-filter.tsx +249 -0
  75. package/src/components/sectioned-list-board/index.ts +19 -0
  76. package/src/components/sectioned-list-board/sectioned-list-board.css +564 -0
  77. package/src/components/sectioned-list-board/sectioned-list-board.tsx +731 -0
  78. package/src/components/sectioned-list-board/sortable-card.tsx +314 -0
  79. package/src/components/sectioned-list-board/sortable-section.tsx +319 -0
  80. package/src/components/sectioned-list-board/types.ts +216 -0
  81. package/src/components/sectioned-list-table/README.md +80 -0
  82. package/src/components/sectioned-list-table/index.ts +14 -0
  83. package/src/components/sectioned-list-table/sectioned-list-table.css +534 -0
  84. package/src/components/sectioned-list-table/sectioned-list-table.tsx +740 -0
  85. package/src/components/sectioned-list-table/sortable-column-header.tsx +120 -0
  86. package/src/components/sectioned-list-table/sortable-row.tsx +420 -0
  87. package/src/components/sectioned-list-table/sortable-section.tsx +251 -0
  88. package/src/components/sectioned-list-table/table-cell-content.tsx +129 -0
  89. package/src/components/sectioned-list-table/types.ts +120 -0
  90. package/src/components/sectioned-list-table/use-column-preferences.ts +103 -0
  91. package/src/components/ui/actions-dropdown.tsx +109 -0
  92. package/src/components/ui/assignee-selector.tsx +209 -0
  93. package/src/components/ui/avatar.tsx +107 -0
  94. package/src/components/ui/breadcrumb.tsx +109 -0
  95. package/src/components/ui/button-group.tsx +83 -0
  96. package/src/components/ui/button.tsx +64 -0
  97. package/src/components/ui/calendar.tsx +220 -0
  98. package/src/components/ui/card.tsx +92 -0
  99. package/src/components/ui/chart.tsx +376 -0
  100. package/src/components/ui/checkbox.tsx +30 -0
  101. package/src/components/ui/collapsible.tsx +33 -0
  102. package/src/components/ui/command.tsx +182 -0
  103. package/src/components/ui/context-menu.tsx +250 -0
  104. package/src/components/ui/create-button-group.tsx +128 -0
  105. package/src/components/ui/dialog.tsx +156 -0
  106. package/src/components/ui/drawer.tsx +133 -0
  107. package/src/components/ui/dropdown-menu.tsx +255 -0
  108. package/src/components/ui/empty.tsx +104 -0
  109. package/src/components/ui/field.tsx +248 -0
  110. package/src/components/ui/form.tsx +165 -0
  111. package/src/components/ui/index.ts +37 -0
  112. package/src/components/ui/input-group.tsx +168 -0
  113. package/src/components/ui/input.tsx +21 -0
  114. package/src/components/ui/kbd.tsx +28 -0
  115. package/src/components/ui/label.tsx +22 -0
  116. package/src/components/ui/navigation-menu.tsx +168 -0
  117. package/src/components/ui/page-header.tsx +80 -0
  118. package/src/components/ui/popover.tsx +87 -0
  119. package/src/components/ui/scroll-area.tsx +56 -0
  120. package/src/components/ui/select.tsx +190 -0
  121. package/src/components/ui/separator.tsx +26 -0
  122. package/src/components/ui/sheet.tsx +141 -0
  123. package/src/components/ui/sidebar.tsx +726 -0
  124. package/src/components/ui/skeleton.tsx +13 -0
  125. package/src/components/ui/sonner.tsx +38 -0
  126. package/src/components/ui/switch.tsx +33 -0
  127. package/src/components/ui/tabs.tsx +91 -0
  128. package/src/components/ui/textarea.tsx +18 -0
  129. package/src/components/ui/toggle-group.tsx +83 -0
  130. package/src/components/ui/toggle.tsx +45 -0
  131. package/src/components/ui/tooltip.tsx +57 -0
  132. package/src/hooks/use-copy-to-clipboard.ts +37 -0
  133. package/src/hooks/use-file-upload.ts +415 -0
  134. package/src/hooks/use-mobile.ts +19 -0
  135. package/src/index.ts +95 -0
  136. package/src/lib/utils.ts +6 -0
  137. package/src/styles.css +1859 -0
  138. package/src/urls.ts +83 -0
  139. package/src/vite.d.ts +22 -0
  140. package/src/vite.js +241 -0
  141. package/tsconfig.base.json +18 -0
  142. package/tsconfig.json +24 -0
@@ -0,0 +1,415 @@
1
+ import type React from "react"
2
+ import {
3
+ useCallback,
4
+ useRef,
5
+ useState,
6
+ type ChangeEvent,
7
+ type DragEvent,
8
+ type InputHTMLAttributes,
9
+ } from "react"
10
+
11
+ export type FileMetadata = {
12
+ name: string
13
+ size: number
14
+ type: string
15
+ url: string
16
+ id: string
17
+ }
18
+
19
+ export type FileWithPreview = {
20
+ file: File | FileMetadata
21
+ id: string
22
+ preview?: string
23
+ }
24
+
25
+ export type FileUploadOptions = {
26
+ maxFiles?: number // Only used when multiple is true, defaults to Infinity
27
+ maxSize?: number // in bytes
28
+ accept?: string
29
+ multiple?: boolean // Defaults to false
30
+ initialFiles?: FileMetadata[]
31
+ onFilesChange?: (files: FileWithPreview[]) => void // Callback when files change
32
+ onFilesAdded?: (addedFiles: FileWithPreview[]) => void // Callback when new files are added
33
+ onError?: (errors: string[]) => void
34
+ }
35
+
36
+ export type FileUploadState = {
37
+ files: FileWithPreview[]
38
+ isDragging: boolean
39
+ errors: string[]
40
+ }
41
+
42
+ export type FileUploadActions = {
43
+ addFiles: (files: FileList | File[]) => void
44
+ removeFile: (id: string) => void
45
+ clearFiles: () => void
46
+ clearErrors: () => void
47
+ handleDragEnter: (e: DragEvent<HTMLElement>) => void
48
+ handleDragLeave: (e: DragEvent<HTMLElement>) => void
49
+ handleDragOver: (e: DragEvent<HTMLElement>) => void
50
+ handleDrop: (e: DragEvent<HTMLElement>) => void
51
+ handleFileChange: (e: ChangeEvent<HTMLInputElement>) => void
52
+ openFileDialog: () => void
53
+ getInputProps: (
54
+ props?: InputHTMLAttributes<HTMLInputElement>
55
+ ) => InputHTMLAttributes<HTMLInputElement> & {
56
+ ref: React.Ref<HTMLInputElement>
57
+ }
58
+ }
59
+
60
+ export const useFileUpload = (
61
+ options: FileUploadOptions = {}
62
+ ): [FileUploadState, FileUploadActions] => {
63
+ const {
64
+ maxFiles = Number.POSITIVE_INFINITY,
65
+ maxSize = Number.POSITIVE_INFINITY,
66
+ accept = "*",
67
+ multiple = false,
68
+ initialFiles = [],
69
+ onFilesChange,
70
+ onFilesAdded,
71
+ onError,
72
+ } = options
73
+
74
+ const [state, setState] = useState<FileUploadState>({
75
+ files: initialFiles.map((file) => ({
76
+ file,
77
+ id: file.id,
78
+ preview: file.url,
79
+ })),
80
+ isDragging: false,
81
+ errors: [],
82
+ })
83
+
84
+ const inputRef = useRef<HTMLInputElement>(null)
85
+
86
+ const validateFile = useCallback(
87
+ (file: File | FileMetadata): string | null => {
88
+ if (file instanceof File) {
89
+ if (file.size > maxSize) {
90
+ return `File "${file.name}" exceeds the maximum size of ${formatBytes(maxSize)}.`
91
+ }
92
+ } else {
93
+ if (file.size > maxSize) {
94
+ return `File "${file.name}" exceeds the maximum size of ${formatBytes(maxSize)}.`
95
+ }
96
+ }
97
+
98
+ if (accept !== "*") {
99
+ const acceptedTypes = accept.split(",").map((type) => type.trim())
100
+ const fileType = file instanceof File ? file.type || "" : file.type
101
+ const fileExtension = `.${file instanceof File ? file.name.split(".").pop() : file.name.split(".").pop()}`
102
+
103
+ const isAccepted = acceptedTypes.some((type) => {
104
+ if (type.startsWith(".")) {
105
+ return fileExtension.toLowerCase() === type.toLowerCase()
106
+ }
107
+ if (type.endsWith("/*")) {
108
+ const baseType = type.split("/")[0]
109
+ return fileType.startsWith(`${baseType}/`)
110
+ }
111
+ return fileType === type
112
+ })
113
+
114
+ if (!isAccepted) {
115
+ return `File "${file instanceof File ? file.name : file.name}" is not an accepted file type.`
116
+ }
117
+ }
118
+
119
+ return null
120
+ },
121
+ [accept, maxSize]
122
+ )
123
+
124
+ const createPreview = useCallback(
125
+ (file: File | FileMetadata): string | undefined => {
126
+ if (file instanceof File) {
127
+ return URL.createObjectURL(file)
128
+ }
129
+ return file.url
130
+ },
131
+ []
132
+ )
133
+
134
+ const generateUniqueId = useCallback((file: File | FileMetadata): string => {
135
+ if (file instanceof File) {
136
+ return `${file.name}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
137
+ }
138
+ return file.id
139
+ }, [])
140
+
141
+ const clearFiles = useCallback(() => {
142
+ setState((prev) => {
143
+ // Clean up object URLs
144
+ for (const file of prev.files) {
145
+ if (
146
+ file.preview &&
147
+ file.file instanceof File &&
148
+ file.file.type.startsWith("image/")
149
+ ) {
150
+ URL.revokeObjectURL(file.preview)
151
+ }
152
+ }
153
+
154
+ if (inputRef.current) {
155
+ inputRef.current.value = ""
156
+ }
157
+
158
+ const newState = {
159
+ ...prev,
160
+ files: [],
161
+ errors: [],
162
+ }
163
+
164
+ onFilesChange?.(newState.files)
165
+ return newState
166
+ })
167
+ }, [onFilesChange])
168
+
169
+ const addFiles = useCallback(
170
+ (newFiles: FileList | File[]) => {
171
+ if (!newFiles || newFiles.length === 0) return
172
+
173
+ const newFilesArray = Array.from(newFiles)
174
+ const errors: string[] = []
175
+
176
+ // Clear existing errors when new files are uploaded
177
+ setState((prev) => ({ ...prev, errors: [] }))
178
+
179
+ // In single file mode, clear existing files first
180
+ if (!multiple) {
181
+ clearFiles()
182
+ }
183
+
184
+ // Check if adding these files would exceed maxFiles (only in multiple mode)
185
+ if (
186
+ multiple &&
187
+ maxFiles !== Number.POSITIVE_INFINITY &&
188
+ state.files.length + newFilesArray.length > maxFiles
189
+ ) {
190
+ errors.push(`You can only upload a maximum of ${maxFiles} files.`)
191
+ onError?.(errors)
192
+ setState((prev) => ({ ...prev, errors }))
193
+ return
194
+ }
195
+
196
+ const validFiles: FileWithPreview[] = []
197
+
198
+ for (const file of newFilesArray) {
199
+ // Only check for duplicates if multiple files are allowed
200
+ if (multiple) {
201
+ const isDuplicate = state.files.some(
202
+ (existingFile) =>
203
+ existingFile.file.name === file.name &&
204
+ existingFile.file.size === file.size
205
+ )
206
+
207
+ // Skip duplicate files silently
208
+ if (isDuplicate) {
209
+ return
210
+ }
211
+ }
212
+
213
+ // Check file size
214
+ if (file.size > maxSize) {
215
+ errors.push(
216
+ multiple
217
+ ? `Some files exceed the maximum size of ${formatBytes(maxSize)}.`
218
+ : `File exceeds the maximum size of ${formatBytes(maxSize)}.`
219
+ )
220
+ continue
221
+ }
222
+
223
+ const error = validateFile(file)
224
+ if (error) {
225
+ errors.push(error)
226
+ } else {
227
+ validFiles.push({
228
+ file,
229
+ id: generateUniqueId(file),
230
+ preview: createPreview(file),
231
+ })
232
+ }
233
+ }
234
+
235
+ // Only update state if we have valid files to add
236
+ if (validFiles.length > 0) {
237
+ // Call the onFilesAdded callback with the newly added valid files
238
+ onFilesAdded?.(validFiles)
239
+
240
+ setState((prev) => {
241
+ const newFiles = !multiple
242
+ ? validFiles
243
+ : [...prev.files, ...validFiles]
244
+ onFilesChange?.(newFiles)
245
+ return {
246
+ ...prev,
247
+ files: newFiles,
248
+ errors,
249
+ }
250
+ })
251
+ } else if (errors.length > 0) {
252
+ onError?.(errors)
253
+ setState((prev) => ({
254
+ ...prev,
255
+ errors,
256
+ }))
257
+ }
258
+
259
+ // Reset input value after handling files
260
+ if (inputRef.current) {
261
+ inputRef.current.value = ""
262
+ }
263
+ },
264
+ [
265
+ state.files,
266
+ maxFiles,
267
+ multiple,
268
+ maxSize,
269
+ validateFile,
270
+ createPreview,
271
+ generateUniqueId,
272
+ clearFiles,
273
+ onFilesChange,
274
+ onFilesAdded,
275
+ ]
276
+ )
277
+
278
+ const removeFile = useCallback(
279
+ (id: string) => {
280
+ setState((prev) => {
281
+ const fileToRemove = prev.files.find((file) => file.id === id)
282
+ if (
283
+ fileToRemove &&
284
+ fileToRemove.preview &&
285
+ fileToRemove.file instanceof File &&
286
+ fileToRemove.file.type.startsWith("image/")
287
+ ) {
288
+ URL.revokeObjectURL(fileToRemove.preview)
289
+ }
290
+
291
+ const newFiles = prev.files.filter((file) => file.id !== id)
292
+ onFilesChange?.(newFiles)
293
+
294
+ return {
295
+ ...prev,
296
+ files: newFiles,
297
+ errors: [],
298
+ }
299
+ })
300
+ },
301
+ [onFilesChange]
302
+ )
303
+
304
+ const clearErrors = useCallback(() => {
305
+ setState((prev) => ({
306
+ ...prev,
307
+ errors: [],
308
+ }))
309
+ }, [])
310
+
311
+ const handleDragEnter = useCallback((e: DragEvent<HTMLElement>) => {
312
+ e.preventDefault()
313
+ e.stopPropagation()
314
+ setState((prev) => ({ ...prev, isDragging: true }))
315
+ }, [])
316
+
317
+ const handleDragLeave = useCallback((e: DragEvent<HTMLElement>) => {
318
+ e.preventDefault()
319
+ e.stopPropagation()
320
+
321
+ if (e.currentTarget.contains(e.relatedTarget as Node)) {
322
+ return
323
+ }
324
+
325
+ setState((prev) => ({ ...prev, isDragging: false }))
326
+ }, [])
327
+
328
+ const handleDragOver = useCallback((e: DragEvent<HTMLElement>) => {
329
+ e.preventDefault()
330
+ e.stopPropagation()
331
+ }, [])
332
+
333
+ const handleDrop = useCallback(
334
+ (e: DragEvent<HTMLElement>) => {
335
+ e.preventDefault()
336
+ e.stopPropagation()
337
+ setState((prev) => ({ ...prev, isDragging: false }))
338
+
339
+ // Don't process files if the input is disabled
340
+ if (inputRef.current?.disabled) {
341
+ return
342
+ }
343
+
344
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
345
+ // In single file mode, only use the first file
346
+ if (!multiple) {
347
+ const file = e.dataTransfer.files[0]
348
+ addFiles([file])
349
+ } else {
350
+ addFiles(e.dataTransfer.files)
351
+ }
352
+ }
353
+ },
354
+ [addFiles, multiple]
355
+ )
356
+
357
+ const handleFileChange = useCallback(
358
+ (e: ChangeEvent<HTMLInputElement>) => {
359
+ if (e.target.files && e.target.files.length > 0) {
360
+ addFiles(e.target.files)
361
+ }
362
+ },
363
+ [addFiles]
364
+ )
365
+
366
+ const openFileDialog = useCallback(() => {
367
+ if (inputRef.current) {
368
+ inputRef.current.click()
369
+ }
370
+ }, [])
371
+
372
+ const getInputProps = useCallback(
373
+ (props: InputHTMLAttributes<HTMLInputElement> = {}) => {
374
+ return {
375
+ ...props,
376
+ type: "file" as const,
377
+ onChange: handleFileChange,
378
+ accept: props.accept || accept,
379
+ multiple: props.multiple !== undefined ? props.multiple : multiple,
380
+ ref: inputRef,
381
+ }
382
+ },
383
+ [accept, multiple, handleFileChange]
384
+ )
385
+
386
+ return [
387
+ state,
388
+ {
389
+ addFiles,
390
+ removeFile,
391
+ clearFiles,
392
+ clearErrors,
393
+ handleDragEnter,
394
+ handleDragLeave,
395
+ handleDragOver,
396
+ handleDrop,
397
+ handleFileChange,
398
+ openFileDialog,
399
+ getInputProps,
400
+ },
401
+ ]
402
+ }
403
+
404
+ // Helper function to format bytes to human-readable format
405
+ export const formatBytes = (bytes: number, decimals = 2): string => {
406
+ if (bytes === 0) return "0 Bytes"
407
+
408
+ const k = 1024
409
+ const dm = decimals < 0 ? 0 : decimals
410
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
411
+
412
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
413
+
414
+ return Number.parseFloat((bytes / k ** i).toFixed(dm)) + sizes[i]
415
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from "react"
2
+
3
+ const MOBILE_BREAKPOINT = 768
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
7
+
8
+ React.useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12
+ }
13
+ mql.addEventListener("change", onChange)
14
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15
+ return () => mql.removeEventListener("change", onChange)
16
+ }, [])
17
+
18
+ return !!isMobile
19
+ }
package/src/index.ts ADDED
@@ -0,0 +1,95 @@
1
+ export { AppShell } from "./AppShell";
2
+ export type {
3
+ AppShellProps,
4
+ CompatibleNavigationFollowEvent,
5
+ CompatibleNavigationItem,
6
+ } from "./AppShell";
7
+
8
+ // Menu types (for settings/user dropdown menus)
9
+ export type {
10
+ MenuDropdownItem,
11
+ MenuDropdownItemGroup,
12
+ MenuDropdownItemOrGroup,
13
+ MenuDropdownItems,
14
+ } from "./components/global-header/ServicesMenu";
15
+ export { basePath, apiUrl, serviceUrl } from "./urls";
16
+
17
+ // Breadcrumb components
18
+ export { AppBreadcrumb } from "./components/layout/AppBreadcrumb";
19
+ export type { AppBreadcrumbProps, BreadcrumbItem } from "./components/layout/AppBreadcrumb";
20
+
21
+ // Page Header components
22
+ export {
23
+ PageHeader,
24
+ PageHeaderTitle,
25
+ PageHeaderDescription,
26
+ PageHeaderActions,
27
+ } from "./components/PageHeader";
28
+ export type { PageHeaderProps } from "./components/PageHeader";
29
+
30
+ // Navigation components
31
+ export { AppNavigation } from "./components/layout/AppNavigation";
32
+ export type {
33
+ AppNavigationProps,
34
+ NavigationFollowEvent,
35
+ NavigationFollowDetail,
36
+ } from "./components/layout/AppNavigation";
37
+
38
+ // Layout components
39
+ export { AppLayout } from "./components/layout/AppLayout";
40
+ export type { AppLayoutProps } from "./components/layout/AppLayout";
41
+
42
+ // Flashbar components (replaces Cloudscape Flashbar)
43
+ export { AppFlashbar } from "./components/layout/AppFlashbar";
44
+ export type { AppFlashbarProps, FlashbarMessage } from "./components/layout/AppFlashbar";
45
+
46
+ // Autocomplete components (based on Base UI)
47
+ export {
48
+ Autocomplete,
49
+ AutocompleteValue,
50
+ AutocompleteInput,
51
+ AutocompletePortal,
52
+ AutocompleteBackdrop,
53
+ AutocompletePositioner,
54
+ AutocompleteContent,
55
+ AutocompleteList,
56
+ AutocompleteItem,
57
+ AutocompleteGroup,
58
+ AutocompleteGroupLabel,
59
+ AutocompleteEmpty,
60
+ AutocompleteClear,
61
+ AutocompleteTrigger,
62
+ } from "./components/search/Autocomplete";
63
+ export type { AutocompleteContentProps } from "./components/search/Autocomplete";
64
+
65
+ // Re-exported from merged design-system package for backwards compatibility
66
+ export const DESIGN_SYSTEM_VERSION = "0.0.0";
67
+
68
+ // UI Components (shadcn/ui + reui)
69
+ export * from "./components/ui";
70
+
71
+ // Pattern Components (reui.io)
72
+ export * from "./components/patterns";
73
+
74
+ // ReUI Components
75
+ export * from "./components/reui";
76
+
77
+ // Hooks
78
+ export * from "./hooks/use-copy-to-clipboard";
79
+ export * from "./hooks/use-file-upload";
80
+ export * from "./hooks/use-mobile";
81
+
82
+ // Section Drawer
83
+ export {
84
+ SectionDrawer,
85
+ DealDrawerContent,
86
+ type SectionDrawerProps,
87
+ type SectionDrawerUser,
88
+ type SectionDrawerActivity,
89
+ type DealDrawerContentProps,
90
+ type DealClient,
91
+ type DealOwner,
92
+ } from "./components/section-drawer";
93
+
94
+ // Utilities
95
+ export { cn } from "./lib/utils";
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }