@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,72 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import { Card, CardContent } from "@/components/ui/card";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ export interface EmptyStateCardProps {
8
+ readonly icon: ReactNode;
9
+ readonly title: string;
10
+ readonly subtitle: string;
11
+ readonly actionLabel?: string;
12
+ readonly onAction?: () => void;
13
+ readonly actionHref?: string;
14
+ readonly actionTarget?: "_blank" | "_self" | "_parent" | "_top";
15
+ readonly actionRel?: string;
16
+ readonly actionDisabled?: boolean;
17
+ readonly actionIcon?: ReactNode;
18
+ readonly className?: string;
19
+ }
20
+
21
+ export function EmptyStateCard({
22
+ icon,
23
+ title,
24
+ subtitle,
25
+ actionLabel,
26
+ onAction,
27
+ actionHref,
28
+ actionTarget = "_self",
29
+ actionRel,
30
+ actionDisabled = false,
31
+ actionIcon,
32
+ className,
33
+ }: EmptyStateCardProps): React.ReactElement {
34
+ const hasLinkAction = actionLabel !== undefined && actionHref !== undefined;
35
+ const hasButtonAction = actionLabel !== undefined && onAction !== undefined;
36
+ const hasDisabledAction = actionLabel !== undefined && actionDisabled;
37
+ const hasAction = hasLinkAction || hasButtonAction || hasDisabledAction;
38
+
39
+ return (
40
+ <Card className={cn("w-full border border-dashed border-border shadow-none ring-0", className)}>
41
+ <CardContent className="flex flex-col items-center justify-start px-6 py-10 text-center">
42
+ <div className="mb-4 flex size-14 items-center justify-center rounded-full bg-muted text-muted-foreground">
43
+ {icon}
44
+ </div>
45
+ <div className="space-y-1.5">
46
+ <h3 className="text-lg font-medium tracking-tight">{title}</h3>
47
+ <p className="max-w-2xl text-sm text-muted-foreground">{subtitle}</p>
48
+ </div>
49
+ {hasAction ? (
50
+ hasLinkAction ? (
51
+ <Button asChild className="mt-5" disabled={actionDisabled}>
52
+ <a href={actionHref} target={actionTarget} rel={actionRel}>
53
+ {actionIcon ? <span className="mr-2 inline-flex items-center">{actionIcon}</span> : null}
54
+ {actionLabel}
55
+ </a>
56
+ </Button>
57
+ ) : hasDisabledAction ? (
58
+ <Button className="mt-5" disabled>
59
+ {actionIcon ? <span className="mr-2 inline-flex items-center">{actionIcon}</span> : null}
60
+ {actionLabel}
61
+ </Button>
62
+ ) : (
63
+ <Button className="mt-5" onClick={onAction} disabled={actionDisabled}>
64
+ {actionIcon ? <span className="mr-2 inline-flex items-center">{actionIcon}</span> : null}
65
+ {actionLabel}
66
+ </Button>
67
+ )
68
+ ) : null}
69
+ </CardContent>
70
+ </Card>
71
+ );
72
+ }
@@ -0,0 +1,8 @@
1
+ export { EmptyStateCard, type EmptyStateCardProps } from "@/components/ui/empty-state/empty-state-card";
2
+ export { InitialEmptyState, type InitialEmptyStateProps } from "@/components/ui/empty-state/initial-empty-state";
3
+ export { NoResultsState, type NoResultsStateProps } from "@/components/ui/empty-state/no-results-state";
4
+ export {
5
+ resolveCollectionEmptyState,
6
+ type CollectionEmptyState,
7
+ type ResolveCollectionEmptyStateOptions,
8
+ } from "@/components/ui/empty-state/collection-empty-state";
@@ -0,0 +1,36 @@
1
+ import { Inbox } from "lucide-react";
2
+ import type { ReactNode } from "react";
3
+
4
+ import { EmptyStateCard } from "@/components/ui/empty-state/empty-state-card";
5
+
6
+ export interface InitialEmptyStateProps {
7
+ readonly icon?: ReactNode;
8
+ readonly title: string;
9
+ readonly subtitle: string;
10
+ readonly actionLabel?: string;
11
+ readonly onAction?: () => void;
12
+ readonly actionIcon?: ReactNode;
13
+ readonly className?: string;
14
+ }
15
+
16
+ export function InitialEmptyState({
17
+ icon = <Inbox className="size-7" />,
18
+ title,
19
+ subtitle,
20
+ actionLabel,
21
+ onAction,
22
+ actionIcon,
23
+ className,
24
+ }: InitialEmptyStateProps): React.ReactElement {
25
+ return (
26
+ <EmptyStateCard
27
+ icon={icon}
28
+ title={title}
29
+ subtitle={subtitle}
30
+ actionLabel={actionLabel}
31
+ onAction={onAction}
32
+ actionIcon={actionIcon}
33
+ className={className}
34
+ />
35
+ );
36
+ }
@@ -0,0 +1,20 @@
1
+ import { SearchX } from "lucide-react";
2
+ import type { ReactNode } from "react";
3
+
4
+ import { EmptyStateCard } from "@/components/ui/empty-state/empty-state-card";
5
+
6
+ export interface NoResultsStateProps {
7
+ readonly icon?: ReactNode;
8
+ readonly title?: string;
9
+ readonly subtitle?: string;
10
+ readonly className?: string;
11
+ }
12
+
13
+ export function NoResultsState({
14
+ icon = <SearchX className="size-7" />,
15
+ title = "No results",
16
+ subtitle = "Try changing your search or filters.",
17
+ className,
18
+ }: NoResultsStateProps): React.ReactElement {
19
+ return <EmptyStateCard icon={icon} title={title} subtitle={subtitle} className={className} />;
20
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "empty-state",
3
+ "description": "Editable source registry entry for empty-state.",
4
+ "importPath": "@carefully-built/ui",
5
+ "exports": [
6
+ "CollectionEmptyState",
7
+ "EmptyStateCard",
8
+ "EmptyStateCardProps",
9
+ "InitialEmptyState",
10
+ "InitialEmptyStateProps",
11
+ "NoResultsState",
12
+ "NoResultsStateProps",
13
+ "ResolveCollectionEmptyStateOptions",
14
+ "resolveCollectionEmptyState"
15
+ ],
16
+ "dependencies": [
17
+ "class-variance-authority",
18
+ "clsx",
19
+ "tailwind-merge"
20
+ ],
21
+ "peerDependencies": [
22
+ "react",
23
+ "react-dom",
24
+ "radix-ui",
25
+ "lucide-react",
26
+ "react-day-picker",
27
+ "vaul"
28
+ ],
29
+ "files": [
30
+ {
31
+ "source": "empty-state/collection-empty-state.ts",
32
+ "target": "components/ui/empty-state/collection-empty-state.ts"
33
+ },
34
+ {
35
+ "source": "empty-state/empty-state-card.tsx",
36
+ "target": "components/ui/empty-state/empty-state-card.tsx"
37
+ },
38
+ {
39
+ "source": "empty-state/index.ts",
40
+ "target": "components/ui/empty-state/index.ts"
41
+ },
42
+ {
43
+ "source": "empty-state/initial-empty-state.tsx",
44
+ "target": "components/ui/empty-state/initial-empty-state.tsx"
45
+ },
46
+ {
47
+ "source": "empty-state/no-results-state.tsx",
48
+ "target": "components/ui/empty-state/no-results-state.tsx"
49
+ },
50
+ {
51
+ "source": "primitives/button.tsx",
52
+ "target": "components/ui/button.tsx"
53
+ },
54
+ {
55
+ "source": "primitives/card.tsx",
56
+ "target": "components/ui/card.tsx"
57
+ },
58
+ {
59
+ "source": "utils/cn.ts",
60
+ "target": "lib/utils.ts"
61
+ }
62
+ ]
63
+ }
@@ -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,80 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Card({
6
+ className,
7
+ size = "default",
8
+ ...props
9
+ }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
10
+ return (
11
+ <div
12
+ data-slot="card"
13
+ data-size={size}
14
+ className={cn("ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-xl py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+
20
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
21
+ return (
22
+ <div
23
+ data-slot="card-header"
24
+ className={cn(
25
+ "gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
26
+ className
27
+ )}
28
+ {...props}
29
+ />
30
+ )
31
+ }
32
+
33
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
34
+ return (
35
+ <div
36
+ data-slot="card-title"
37
+ className={cn("text-base leading-snug font-medium group-data-[size=sm]/card:text-sm", className)}
38
+ {...props}
39
+ />
40
+ )
41
+ }
42
+
43
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
44
+ return (
45
+ <div
46
+ data-slot="card-description"
47
+ className={cn("text-muted-foreground text-sm", className)}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
54
+ return (
55
+ <div
56
+ data-slot="card-content"
57
+ className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
58
+ {...props}
59
+ />
60
+ )
61
+ }
62
+
63
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
64
+ return (
65
+ <div
66
+ data-slot="card-footer"
67
+ className={cn("bg-muted/50 rounded-b-xl border-t p-4 group-data-[size=sm]/card:p-3 flex items-center", className)}
68
+ {...props}
69
+ />
70
+ )
71
+ }
72
+
73
+ export {
74
+ Card,
75
+ CardHeader,
76
+ CardFooter,
77
+ CardTitle,
78
+ CardDescription,
79
+ CardContent,
80
+ }
@@ -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,16 @@
1
+ import { cn } from "@/lib/utils";
2
+
3
+ export interface ErrorCodeProps {
4
+ readonly code: string;
5
+ readonly reference?: string | null;
6
+ readonly className?: string;
7
+ }
8
+
9
+ export function ErrorCode({ code, reference, className }: ErrorCodeProps): React.ReactElement {
10
+ return (
11
+ <p className={cn("text-xs text-muted-foreground/55", className)}>
12
+ Codice {code}
13
+ {reference ? <span className="ml-2">Rif. {reference}</span> : null}
14
+ </p>
15
+ );
16
+ }
@@ -0,0 +1,75 @@
1
+ export type ErrorPageKind = "error" | "not-found";
2
+
3
+ export interface ErrorPageContent {
4
+ readonly code: string;
5
+ readonly title: string;
6
+ readonly description: string;
7
+ readonly shouldCapture: boolean;
8
+ }
9
+
10
+ export interface ResolveErrorPageContentOptions {
11
+ readonly fallbackCode?: string;
12
+ readonly fallbackTitle?: string;
13
+ readonly fallbackDescription?: string;
14
+ }
15
+
16
+ const NOT_FOUND_STATUS = 404;
17
+
18
+ function getErrorText(error: unknown): string {
19
+ if (error instanceof Error) {
20
+ const digest = "digest" in error ? String(error.digest) : "";
21
+ return `${error.message} ${digest}`;
22
+ }
23
+
24
+ if (typeof error === "object" && error !== null) {
25
+ const record = error as { readonly message?: unknown; readonly digest?: unknown };
26
+ const message = typeof record.message === "string" ? record.message : "";
27
+ const digest = typeof record.digest === "string" ? record.digest : "";
28
+ return `${message} ${digest}`;
29
+ }
30
+
31
+ return typeof error === "string" ? error : "";
32
+ }
33
+
34
+ function getErrorStatusCode(error: unknown): number | null {
35
+ const text = getErrorText(error);
36
+ const fallbackMatch = /NEXT_HTTP_ERROR_FALLBACK;(\d{3})/.exec(text);
37
+
38
+ if (fallbackMatch) {
39
+ return Number(fallbackMatch[1]);
40
+ }
41
+
42
+ const statusMatch = /\bstatus(?:Code)?[=:]\s*(\d{3})\b/i.exec(text);
43
+ return statusMatch ? Number(statusMatch[1]) : null;
44
+ }
45
+
46
+ export function resolveErrorPageContent(
47
+ error: unknown,
48
+ options: ResolveErrorPageContentOptions = {},
49
+ ): ErrorPageContent {
50
+ if (getErrorStatusCode(error) === NOT_FOUND_STATUS) {
51
+ return {
52
+ code: "404",
53
+ title: "Page not found",
54
+ description: "The page you are looking for does not exist or has been moved.",
55
+ shouldCapture: false,
56
+ };
57
+ }
58
+
59
+ return {
60
+ code: options.fallbackCode ?? "500",
61
+ title: options.fallbackTitle ?? "An error occurred",
62
+ description:
63
+ options.fallbackDescription ?? "An unexpected error occurred. Please try again later.",
64
+ shouldCapture: true,
65
+ };
66
+ }
67
+
68
+ export function getNotFoundPageContent(): ErrorPageContent {
69
+ return {
70
+ code: "404",
71
+ title: "Page not found",
72
+ description: "The page you are looking for does not exist or has been moved.",
73
+ shouldCapture: false,
74
+ };
75
+ }
@@ -0,0 +1,19 @@
1
+ export { ErrorCode, type ErrorCodeProps } from "@/components/ui/error-page/error-code";
2
+ export {
3
+ getNotFoundPageContent,
4
+ resolveErrorPageContent,
5
+ type ErrorPageContent,
6
+ type ErrorPageKind,
7
+ type ResolveErrorPageContentOptions,
8
+ } from "@/components/ui/error-page/error-page-content";
9
+ export {
10
+ captureErrorToPostHog,
11
+ createErrorReference,
12
+ type PostHogErrorCapturePayload,
13
+ } from "@/components/ui/error-page/posthog-error-capture";
14
+ export {
15
+ SaasErrorPage,
16
+ SaasNotFoundPage,
17
+ type SaasErrorPageProps,
18
+ type SaasNotFoundPageProps,
19
+ } from "@/components/ui/error-page/saas-error-page";
@@ -0,0 +1,83 @@
1
+ export interface PostHogErrorCapturePayload {
2
+ readonly error: unknown;
3
+ readonly code: string;
4
+ readonly reference: string;
5
+ readonly source?: string;
6
+ readonly metadata?: Record<string, unknown>;
7
+ }
8
+
9
+ interface PostHogClient {
10
+ capture: (eventName: string, properties?: Record<string, unknown>) => void;
11
+ }
12
+
13
+ function getPostHogClient(): PostHogClient | null {
14
+ if (typeof window === "undefined") {
15
+ return null;
16
+ }
17
+
18
+ const client = (window as typeof window & { readonly posthog?: PostHogClient }).posthog;
19
+ return typeof client?.capture === "function" ? client : null;
20
+ }
21
+
22
+ function normalizeError(error: unknown): {
23
+ readonly name: string;
24
+ readonly message: string;
25
+ readonly stack?: string;
26
+ readonly digest?: string;
27
+ } {
28
+ if (error instanceof Error) {
29
+ const digest = "digest" in error && typeof error.digest === "string" ? error.digest : undefined;
30
+
31
+ return {
32
+ name: error.name,
33
+ message: error.message,
34
+ stack: error.stack,
35
+ digest,
36
+ };
37
+ }
38
+
39
+ return {
40
+ name: "UnknownError",
41
+ message: typeof error === "string" ? error : "Unknown error",
42
+ };
43
+ }
44
+
45
+ export function createErrorReference(error: unknown): string {
46
+ if (error instanceof Error && "digest" in error && typeof error.digest === "string") {
47
+ return error.digest;
48
+ }
49
+
50
+ return `err_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
51
+ }
52
+
53
+ export function captureErrorToPostHog({
54
+ error,
55
+ code,
56
+ reference,
57
+ source = "error-page",
58
+ metadata,
59
+ }: PostHogErrorCapturePayload): void {
60
+ const posthog = getPostHogClient();
61
+
62
+ if (!posthog) {
63
+ return;
64
+ }
65
+
66
+ const normalizedError = normalizeError(error);
67
+
68
+ try {
69
+ posthog.capture("$exception", {
70
+ $exception_type: normalizedError.name,
71
+ $exception_message: normalizedError.message,
72
+ $exception_stack_trace: normalizedError.stack,
73
+ code,
74
+ digest: normalizedError.digest,
75
+ error_reference: reference,
76
+ pathname: window.location.pathname,
77
+ source,
78
+ ...metadata,
79
+ });
80
+ } catch {
81
+ // Error pages must never fail while reporting an error.
82
+ }
83
+ }