@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,99 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Drawer as DrawerPrimitive } from 'vaul';
5
+
6
+ import { cn } from '@/lib/utils';
7
+
8
+ function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
9
+ return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
10
+ }
11
+
12
+ function DrawerPortal({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
13
+ return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
14
+ }
15
+
16
+ function DrawerOverlay({
17
+ className,
18
+ ...props
19
+ }: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
20
+ return (
21
+ <DrawerPrimitive.Overlay
22
+ data-slot="drawer-overlay"
23
+ className={cn(
24
+ 'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 backdrop-blur-xs',
25
+ className,
26
+ )}
27
+ {...props}
28
+ />
29
+ );
30
+ }
31
+
32
+ function DrawerContent({
33
+ className,
34
+ children,
35
+ ...props
36
+ }: React.ComponentProps<typeof DrawerPrimitive.Content>) {
37
+ return (
38
+ <DrawerPortal data-slot="drawer-portal">
39
+ <DrawerOverlay />
40
+ <DrawerPrimitive.Content
41
+ data-slot="drawer-content"
42
+ className={cn(
43
+ 'bg-background group/drawer-content fixed z-50 flex h-auto flex-col overflow-visible text-sm data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-xl data-[vaul-drawer-direction=bottom]:border-t data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:rounded-r-xl data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:rounded-l-xl data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-xl data-[vaul-drawer-direction=top]:border-b data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm',
44
+ className,
45
+ )}
46
+ {...props}
47
+ >
48
+ <div className="bg-muted mx-auto mt-4 hidden h-1 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
49
+ {children}
50
+ </DrawerPrimitive.Content>
51
+ </DrawerPortal>
52
+ );
53
+ }
54
+
55
+ function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {
56
+ return (
57
+ <div
58
+ data-slot="drawer-header"
59
+ className={cn(
60
+ 'flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-0.5 md:text-left',
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ function DrawerTitle({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Title>) {
69
+ return (
70
+ <DrawerPrimitive.Title
71
+ data-slot="drawer-title"
72
+ className={cn('text-foreground text-base font-medium', className)}
73
+ {...props}
74
+ />
75
+ );
76
+ }
77
+
78
+ function DrawerDescription({
79
+ className,
80
+ ...props
81
+ }: React.ComponentProps<typeof DrawerPrimitive.Description>) {
82
+ return (
83
+ <DrawerPrimitive.Description
84
+ data-slot="drawer-description"
85
+ className={cn('text-muted-foreground text-sm', className)}
86
+ {...props}
87
+ />
88
+ );
89
+ }
90
+
91
+ export {
92
+ Drawer,
93
+ DrawerContent,
94
+ DrawerDescription,
95
+ DrawerHeader,
96
+ DrawerOverlay,
97
+ DrawerPortal,
98
+ DrawerTitle,
99
+ };
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+
3
+ import { Command } from 'lucide-react';
4
+
5
+ import type { ReactNode } from 'react';
6
+
7
+ import { cn } from '@/lib/utils';
8
+
9
+ export function KeyboardKeycap({
10
+ children,
11
+ className,
12
+ }: {
13
+ readonly children: ReactNode;
14
+ readonly className?: string;
15
+ }): React.ReactElement {
16
+ return (
17
+ <span
18
+ className={cn(
19
+ 'inline-flex h-4 min-w-4 items-center justify-center rounded-[4px] border px-1 text-[9px] font-semibold leading-none',
20
+ className,
21
+ )}
22
+ >
23
+ {children}
24
+ </span>
25
+ );
26
+ }
27
+
28
+ export function ShortcutModifierKeycap({
29
+ modifierLabel,
30
+ className,
31
+ }: {
32
+ readonly modifierLabel: string;
33
+ readonly className?: string;
34
+ }): React.ReactElement {
35
+ return (
36
+ <KeyboardKeycap className={className}>
37
+ {modifierLabel === 'Cmd' ? <Command className="size-[10px]" /> : 'Ctrl'}
38
+ </KeyboardKeycap>
39
+ );
40
+ }
@@ -0,0 +1,103 @@
1
+ 'use client';
2
+
3
+ import { X } from 'lucide-react';
4
+ import * as React from 'react';
5
+ import { Dialog as SheetPrimitive } from 'radix-ui';
6
+
7
+ import { Button } from '@/components/ui/button';
8
+ import { cn } from '@/lib/utils';
9
+
10
+ function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
11
+ return <SheetPrimitive.Root data-slot="sheet" {...props} />;
12
+ }
13
+
14
+ function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
15
+ return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
16
+ }
17
+
18
+ function SheetOverlay({
19
+ className,
20
+ ...props
21
+ }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
22
+ return (
23
+ <SheetPrimitive.Overlay
24
+ data-slot="sheet-overlay"
25
+ className={cn(
26
+ 'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 backdrop-blur-xs duration-100 data-ending-style:opacity-0 data-starting-style:opacity-0',
27
+ className,
28
+ )}
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ function SheetContent({
35
+ className,
36
+ children,
37
+ side = 'right',
38
+ showCloseButton = true,
39
+ ...props
40
+ }: React.ComponentProps<typeof SheetPrimitive.Content> & {
41
+ side?: 'top' | 'right' | 'bottom' | 'left';
42
+ showCloseButton?: boolean;
43
+ }) {
44
+ return (
45
+ <SheetPortal>
46
+ <SheetOverlay />
47
+ <SheetPrimitive.Content
48
+ data-slot="sheet-content"
49
+ data-side={side}
50
+ className={cn(
51
+ 'bg-background data-open:animate-in data-closed:animate-out data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col gap-4 overflow-visible bg-clip-padding text-sm shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:rounded-r-xl data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:rounded-l-xl data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',
52
+ className,
53
+ )}
54
+ {...props}
55
+ >
56
+ {children}
57
+ {showCloseButton ? (
58
+ <SheetPrimitive.Close data-slot="sheet-close" asChild>
59
+ <Button variant="ghost" className="absolute top-3 right-3" size="icon-sm">
60
+ <X className="size-4" />
61
+ <span className="sr-only">Close</span>
62
+ </Button>
63
+ </SheetPrimitive.Close>
64
+ ) : null}
65
+ </SheetPrimitive.Content>
66
+ </SheetPortal>
67
+ );
68
+ }
69
+
70
+ function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
71
+ return (
72
+ <div
73
+ data-slot="sheet-header"
74
+ className={cn('flex flex-col gap-0.5 p-4', className)}
75
+ {...props}
76
+ />
77
+ );
78
+ }
79
+
80
+ function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
81
+ return (
82
+ <SheetPrimitive.Title
83
+ data-slot="sheet-title"
84
+ className={cn('text-foreground text-base font-medium', className)}
85
+ {...props}
86
+ />
87
+ );
88
+ }
89
+
90
+ function SheetDescription({
91
+ className,
92
+ ...props
93
+ }: React.ComponentProps<typeof SheetPrimitive.Description>) {
94
+ return (
95
+ <SheetPrimitive.Description
96
+ data-slot="sheet-description"
97
+ className={cn('text-muted-foreground text-sm', className)}
98
+ {...props}
99
+ />
100
+ );
101
+ }
102
+
103
+ export { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle };
@@ -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,28 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+
5
+ export function useMediaQuery(query: string, defaultValue = false): boolean {
6
+ const [matches, setMatches] = useState(defaultValue);
7
+
8
+ useEffect(() => {
9
+ const mediaQuery = window.matchMedia(query);
10
+
11
+ const handleChange = (): void => {
12
+ setMatches(mediaQuery.matches);
13
+ };
14
+
15
+ handleChange();
16
+ mediaQuery.addEventListener('change', handleChange);
17
+
18
+ return () => {
19
+ mediaQuery.removeEventListener('change', handleChange);
20
+ };
21
+ }, [query]);
22
+
23
+ return matches;
24
+ }
25
+
26
+ export function useIsMobile(maxWidth = 767): boolean {
27
+ return useMediaQuery(`(max-width: ${String(maxWidth)}px)`);
28
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "responsive-sheet.footer",
3
+ "description": "Editable source registry entry for responsive-sheet.footer.",
4
+ "importPath": "@carefully-built/ui",
5
+ "exports": [
6
+ "DesktopConfirmShortcutHint",
7
+ "SheetActionFooter"
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": "primitives/button.tsx",
29
+ "target": "components/ui/button.tsx"
30
+ },
31
+ {
32
+ "source": "primitives/keyboard-shortcut-hint.tsx",
33
+ "target": "components/ui/keyboard-shortcut-hint.tsx"
34
+ },
35
+ {
36
+ "source": "utils/cn.ts",
37
+ "target": "lib/utils.ts"
38
+ }
39
+ ]
40
+ }
@@ -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,89 @@
1
+ import * as React from "react";
2
+ import { cva } from "class-variance-authority";
3
+ import { Slot } from "radix-ui";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const buttonVariants = cva(
8
+ "group/button inline-flex shrink-0 cursor-pointer items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground hover:brightness-90 [a]:hover:bg-primary/80",
14
+ outline:
15
+ "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
16
+ secondary:
17
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
18
+ ghost:
19
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
20
+ destructive:
21
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
22
+ link: "text-primary underline-offset-4 hover:underline",
23
+ },
24
+ size: {
25
+ default:
26
+ "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
27
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
28
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
29
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
30
+ icon: "size-8",
31
+ "icon-xs":
32
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
33
+ "icon-sm":
34
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
35
+ "icon-lg": "size-9",
36
+ },
37
+ },
38
+ defaultVariants: {
39
+ variant: "default",
40
+ size: "default",
41
+ },
42
+ },
43
+ );
44
+
45
+ type ButtonVariant =
46
+ | "default"
47
+ | "outline"
48
+ | "secondary"
49
+ | "ghost"
50
+ | "destructive"
51
+ | "link";
52
+ type ButtonSize =
53
+ | "default"
54
+ | "xs"
55
+ | "sm"
56
+ | "lg"
57
+ | "icon"
58
+ | "icon-xs"
59
+ | "icon-sm"
60
+ | "icon-lg";
61
+
62
+ interface ButtonProps extends React.ComponentProps<"button"> {
63
+ readonly asChild?: boolean;
64
+ readonly size?: ButtonSize;
65
+ readonly variant?: ButtonVariant;
66
+ }
67
+
68
+ function Button({
69
+ className,
70
+ variant = "default",
71
+ size = "default",
72
+ asChild = false,
73
+ ...props
74
+ }: ButtonProps) {
75
+ const Comp = asChild ? Slot.Root : "button";
76
+
77
+ return (
78
+ <Comp
79
+ data-slot="button"
80
+ data-variant={variant}
81
+ data-size={size}
82
+ className={cn(buttonVariants({ variant, size, className }))}
83
+ {...props}
84
+ />
85
+ );
86
+ }
87
+
88
+ export { Button, buttonVariants };
89
+ export type { ButtonProps, ButtonSize, ButtonVariant };
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+
3
+ import { Command } from 'lucide-react';
4
+
5
+ import type { ReactNode } from 'react';
6
+
7
+ import { cn } from '@/lib/utils';
8
+
9
+ export function KeyboardKeycap({
10
+ children,
11
+ className,
12
+ }: {
13
+ readonly children: ReactNode;
14
+ readonly className?: string;
15
+ }): React.ReactElement {
16
+ return (
17
+ <span
18
+ className={cn(
19
+ 'inline-flex h-4 min-w-4 items-center justify-center rounded-[4px] border px-1 text-[9px] font-semibold leading-none',
20
+ className,
21
+ )}
22
+ >
23
+ {children}
24
+ </span>
25
+ );
26
+ }
27
+
28
+ export function ShortcutModifierKeycap({
29
+ modifierLabel,
30
+ className,
31
+ }: {
32
+ readonly modifierLabel: string;
33
+ readonly className?: string;
34
+ }): React.ReactElement {
35
+ return (
36
+ <KeyboardKeycap className={className}>
37
+ {modifierLabel === 'Cmd' ? <Command className="size-[10px]" /> : 'Ctrl'}
38
+ </KeyboardKeycap>
39
+ );
40
+ }
@@ -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,34 @@
1
+ {
2
+ "name": "responsive-sheet.shortcuts",
3
+ "description": "Editable source registry entry for responsive-sheet.shortcuts.",
4
+ "importPath": "@carefully-built/ui",
5
+ "exports": [
6
+ "getDesktopShortcutModifierLabel",
7
+ "isAllowedConfirmShortcutEvent",
8
+ "useDesktopConfirmShortcut",
9
+ "useDesktopShortcutModifierLabel"
10
+ ],
11
+ "dependencies": [
12
+ "class-variance-authority",
13
+ "clsx",
14
+ "tailwind-merge"
15
+ ],
16
+ "peerDependencies": [
17
+ "react",
18
+ "react-dom",
19
+ "radix-ui",
20
+ "lucide-react",
21
+ "react-day-picker",
22
+ "vaul"
23
+ ],
24
+ "files": [
25
+ {
26
+ "source": "overlays/responsive-sheet.shortcuts.ts",
27
+ "target": "components/ui/responsive-sheet.shortcuts.ts"
28
+ },
29
+ {
30
+ "source": "utils/cn.ts",
31
+ "target": "lib/utils.ts"
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,103 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ interface NavigatorWithUserAgentData extends Navigator {
4
+ readonly userAgentData?: {
5
+ readonly platform?: string;
6
+ };
7
+ }
8
+
9
+ export function getDesktopShortcutModifierLabel(): string {
10
+ if (typeof navigator === "undefined") {
11
+ return "Ctrl";
12
+ }
13
+
14
+ const navigatorWithUserAgentData = navigator as NavigatorWithUserAgentData;
15
+ const platform =
16
+ navigatorWithUserAgentData.userAgentData?.platform ?? navigator.userAgent;
17
+
18
+ return /Mac|iPhone|iPad|iPod/i.test(platform) ? "Cmd" : "Ctrl";
19
+ }
20
+
21
+ export function isAllowedConfirmShortcutEvent(
22
+ event: KeyboardEvent,
23
+ desktopModifierLabel: string,
24
+ ): boolean {
25
+ if (event.key !== "Enter" || event.repeat || event.isComposing) {
26
+ return false;
27
+ }
28
+
29
+ const expectsMetaKey = desktopModifierLabel === "Cmd";
30
+ const usedExpectedModifier = expectsMetaKey ? event.metaKey : event.ctrlKey;
31
+ const usedOtherModifier = expectsMetaKey ? event.ctrlKey : event.metaKey;
32
+ const usedShiftModifier = event.shiftKey;
33
+ const usedAltModifier = event.altKey;
34
+
35
+ if (
36
+ !usedExpectedModifier ||
37
+ usedOtherModifier ||
38
+ usedShiftModifier ||
39
+ usedAltModifier
40
+ ) {
41
+ return false;
42
+ }
43
+
44
+ return true;
45
+ }
46
+
47
+ export function useDesktopShortcutModifierLabel(
48
+ enabled: boolean,
49
+ ): string | null {
50
+ const [desktopModifierLabel, setDesktopModifierLabel] = useState<
51
+ string | null
52
+ >(null);
53
+
54
+ useEffect(() => {
55
+ if (!enabled) {
56
+ setDesktopModifierLabel(null);
57
+ return;
58
+ }
59
+
60
+ setDesktopModifierLabel(getDesktopShortcutModifierLabel());
61
+ }, [enabled]);
62
+
63
+ return desktopModifierLabel;
64
+ }
65
+
66
+ interface UseDesktopConfirmShortcutOptions {
67
+ readonly open: boolean;
68
+ readonly enabled: boolean;
69
+ readonly confirmDisabled: boolean;
70
+ readonly confirmLoading: boolean;
71
+ readonly onConfirm?: () => void;
72
+ }
73
+
74
+ export function useDesktopConfirmShortcut({
75
+ open,
76
+ enabled,
77
+ confirmDisabled,
78
+ confirmLoading,
79
+ onConfirm,
80
+ }: UseDesktopConfirmShortcutOptions): void {
81
+ useEffect(() => {
82
+ if (!open || !enabled || !onConfirm || confirmDisabled || confirmLoading) {
83
+ return;
84
+ }
85
+
86
+ const desktopModifierLabel = getDesktopShortcutModifierLabel();
87
+
88
+ const handleKeyDown = (event: KeyboardEvent): void => {
89
+ if (!isAllowedConfirmShortcutEvent(event, desktopModifierLabel)) {
90
+ return;
91
+ }
92
+
93
+ event.preventDefault();
94
+ onConfirm();
95
+ };
96
+
97
+ window.addEventListener("keydown", handleKeyDown);
98
+
99
+ return () => {
100
+ window.removeEventListener("keydown", handleKeyDown);
101
+ };
102
+ }, [confirmDisabled, confirmLoading, enabled, onConfirm, open]);
103
+ }