@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,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
+ }
@@ -0,0 +1,132 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+
5
+ import { SheetActionFooter } from '@/components/ui/responsive-sheet.footer';
6
+ import { DesktopSheetLayout, MobileSheetLayout } from '@/components/ui/responsive-sheet.layouts';
7
+ import {
8
+ useDesktopConfirmShortcut,
9
+ useDesktopShortcutModifierLabel,
10
+ } from '@/components/ui/responsive-sheet.shortcuts';
11
+ import { cn } from '@/lib/utils';
12
+ import { useIsMobile } from '@/components/ui/use-media-query';
13
+
14
+ export interface SheetOutsideInteractionGuard {
15
+ readonly selectors: readonly string[];
16
+ }
17
+
18
+ export interface ResponsiveSheetClassNames {
19
+ readonly desktopContent?: string;
20
+ readonly mobileContent?: string;
21
+ readonly header?: string;
22
+ readonly body?: string;
23
+ readonly footer?: string;
24
+ readonly title?: string;
25
+ readonly description?: string;
26
+ }
27
+
28
+ export interface ResponsiveSheetProps {
29
+ readonly open: boolean;
30
+ readonly onOpenChange: (open: boolean) => void;
31
+ readonly title: ReactNode;
32
+ readonly description?: ReactNode;
33
+ readonly children: ReactNode;
34
+ readonly footer?: ReactNode;
35
+ readonly onCancel?: () => void;
36
+ readonly cancelLabel?: ReactNode;
37
+ readonly onConfirm?: () => void;
38
+ readonly confirmLabel?: ReactNode;
39
+ readonly confirmDisabled?: boolean;
40
+ readonly confirmLoading?: boolean;
41
+ readonly width?: number;
42
+ readonly modal?: boolean;
43
+ readonly outsideInteractionGuard?: SheetOutsideInteractionGuard;
44
+ readonly enableDesktopConfirmShortcut?: boolean;
45
+ readonly mobileDrawerContentClassName?: string;
46
+ readonly className?: string;
47
+ readonly contentClassName?: string;
48
+ readonly footerClassName?: string;
49
+ readonly classes?: ResponsiveSheetClassNames;
50
+ }
51
+
52
+ export function ResponsiveSheet({
53
+ open,
54
+ onOpenChange,
55
+ title,
56
+ description,
57
+ children,
58
+ footer,
59
+ onCancel,
60
+ cancelLabel = 'Cancel',
61
+ onConfirm,
62
+ confirmLabel = 'Save',
63
+ confirmDisabled = false,
64
+ confirmLoading = false,
65
+ width = 550,
66
+ modal = true,
67
+ outsideInteractionGuard,
68
+ enableDesktopConfirmShortcut = true,
69
+ mobileDrawerContentClassName,
70
+ className,
71
+ contentClassName,
72
+ footerClassName,
73
+ classes,
74
+ }: ResponsiveSheetProps): React.ReactElement {
75
+ const isMobile = useIsMobile();
76
+ const desktopConfirmShortcutEnabled =
77
+ !isMobile && enableDesktopConfirmShortcut && Boolean(onConfirm);
78
+ const desktopModifierLabel = useDesktopShortcutModifierLabel(desktopConfirmShortcutEnabled);
79
+
80
+ useDesktopConfirmShortcut({
81
+ open,
82
+ enabled: desktopConfirmShortcutEnabled,
83
+ confirmDisabled,
84
+ confirmLoading,
85
+ onConfirm: onConfirm
86
+ ? () => {
87
+ onConfirm();
88
+ }
89
+ : undefined,
90
+ });
91
+
92
+ const resolvedFooter = (
93
+ <SheetActionFooter
94
+ footer={footer}
95
+ onCancel={onCancel}
96
+ cancelLabel={cancelLabel}
97
+ onConfirm={onConfirm}
98
+ confirmLabel={confirmLabel}
99
+ confirmDisabled={confirmDisabled}
100
+ confirmLoading={confirmLoading}
101
+ desktopConfirmShortcutEnabled={desktopConfirmShortcutEnabled}
102
+ desktopModifierLabel={desktopModifierLabel}
103
+ />
104
+ );
105
+ const hasFooter = [footer, onCancel, onConfirm].some(
106
+ (value) => value !== null && value !== undefined,
107
+ );
108
+ const sharedLayoutProps = {
109
+ open,
110
+ onOpenChange,
111
+ modal,
112
+ outsideInteractionGuard,
113
+ title,
114
+ description,
115
+ footer: hasFooter ? resolvedFooter : null,
116
+ children,
117
+ contentClassName,
118
+ footerClassName,
119
+ mobileDrawerContentClassName,
120
+ classes: {
121
+ ...classes,
122
+ desktopContent: cn(className, classes?.desktopContent),
123
+ mobileContent: cn(className, mobileDrawerContentClassName, classes?.mobileContent),
124
+ },
125
+ };
126
+
127
+ return isMobile ? (
128
+ <MobileSheetLayout {...sharedLayoutProps} />
129
+ ) : (
130
+ <DesktopSheetLayout {...sharedLayoutProps} width={width} />
131
+ );
132
+ }
@@ -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,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,63 @@
1
+ "use client";
2
+
3
+ import { CircleHelp } from "lucide-react";
4
+ import { useState, type ReactNode } from "react";
5
+
6
+ import { ResponsiveSheet } from "@/components/ui/responsive-sheet";
7
+ import { Button } from "@/components/ui/button";
8
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
9
+
10
+ export interface HelpInfoButtonProps {
11
+ readonly ariaLabel: string;
12
+ readonly tooltip: ReactNode;
13
+ readonly title: ReactNode;
14
+ readonly description?: ReactNode;
15
+ readonly children: ReactNode;
16
+ readonly width?: number;
17
+ }
18
+
19
+ export function HelpInfoButton({
20
+ ariaLabel,
21
+ tooltip,
22
+ title,
23
+ description,
24
+ children,
25
+ width = 620,
26
+ }: HelpInfoButtonProps): React.ReactElement {
27
+ const [open, setOpen] = useState(false);
28
+
29
+ return (
30
+ <>
31
+ <Tooltip>
32
+ <TooltipTrigger asChild>
33
+ <Button
34
+ type="button"
35
+ variant="ghost"
36
+ size="icon"
37
+ className="size-6 text-muted-foreground hover:text-foreground"
38
+ aria-label={ariaLabel}
39
+ onClick={() => {
40
+ setOpen(true);
41
+ }}
42
+ >
43
+ <CircleHelp className="size-3.5" />
44
+ </Button>
45
+ </TooltipTrigger>
46
+ <TooltipContent>{tooltip}</TooltipContent>
47
+ </Tooltip>
48
+ <ResponsiveSheet
49
+ open={open}
50
+ onOpenChange={setOpen}
51
+ title={title}
52
+ description={description}
53
+ cancelLabel="Close"
54
+ onCancel={() => {
55
+ setOpen(false);
56
+ }}
57
+ width={width}
58
+ >
59
+ {children}
60
+ </ResponsiveSheet>
61
+ </>
62
+ );
63
+ }
@@ -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,57 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Tooltip as TooltipPrimitive } from "radix-ui"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function TooltipProvider({
9
+ delayDuration = 0,
10
+ ...props
11
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
12
+ return (
13
+ <TooltipPrimitive.Provider
14
+ data-slot="tooltip-provider"
15
+ delayDuration={delayDuration}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ function Tooltip({
22
+ ...props
23
+ }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
24
+ return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
25
+ }
26
+
27
+ function TooltipTrigger({
28
+ ...props
29
+ }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
30
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
31
+ }
32
+
33
+ function TooltipContent({
34
+ className,
35
+ sideOffset = 0,
36
+ children,
37
+ ...props
38
+ }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
39
+ return (
40
+ <TooltipPrimitive.Portal>
41
+ <TooltipPrimitive.Content
42
+ data-slot="tooltip-content"
43
+ sideOffset={sideOffset}
44
+ className={cn(
45
+ "data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 rounded-md bg-foreground px-3 py-1.5 text-xs text-background z-50 w-fit max-w-[min(20rem,calc(100vw-1.5rem))] whitespace-normal break-words origin-(--radix-tooltip-content-transform-origin)",
46
+ className
47
+ )}
48
+ {...props}
49
+ >
50
+ {children}
51
+ <TooltipPrimitive.Arrow className="size-2.5 rotate-45 rounded-[2px] bg-foreground fill-foreground z-50 translate-y-[calc(-50%_-_2px)]" />
52
+ </TooltipPrimitive.Content>
53
+ </TooltipPrimitive.Portal>
54
+ )
55
+ }
56
+
57
+ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
@@ -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
+ }