create-feedbax 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/LICENSE +201 -21
  2. package/README.md +11 -1
  3. package/dist/cli.js +1261 -26
  4. package/dist/index.js +1402 -160
  5. package/package.json +40 -14
  6. package/template/.feedbax-template.sha256 +1 -0
  7. package/template/.gitignore.template +6 -0
  8. package/template/.npmignore.template +5 -0
  9. package/template/.prettierignore.template +4 -0
  10. package/template/LICENSE +201 -0
  11. package/template/components.json +25 -0
  12. package/template/eslint.config.mjs +25 -0
  13. package/template/package.json +70 -14
  14. package/template/public/feedbax-mark.svg +4 -0
  15. package/template/public/robots.txt +3 -0
  16. package/template/src/authorized-draft-post.test.tsx +212 -0
  17. package/template/src/authorized-draft-post.tsx +204 -0
  18. package/template/src/authorized-draft-query.ts +74 -0
  19. package/template/src/browser-post-state.test.ts +120 -0
  20. package/template/src/browser-post-state.ts +134 -0
  21. package/template/src/browser-vote-state.test.ts +14 -0
  22. package/template/src/browser-vote-state.ts +27 -0
  23. package/template/src/capability-authorization-error.ts +3 -0
  24. package/template/src/changelog-page.test.tsx +139 -0
  25. package/template/src/changelog-page.tsx +219 -0
  26. package/template/src/changelog-query.ts +13 -0
  27. package/template/src/changelog-runtime.ts +27 -0
  28. package/template/src/cloudflare-turnstile.ts +31 -0
  29. package/template/src/cloudflare-workers-test.ts +3 -0
  30. package/template/src/cloudflare-workers.d.ts +18 -0
  31. package/template/src/comment-creation.test.tsx +270 -0
  32. package/template/src/comment-profile.ts +5 -0
  33. package/template/src/comment-thread-page.ts +18 -0
  34. package/template/src/components/command-palette-context.tsx +71 -0
  35. package/template/src/components/command-palette.tsx +246 -0
  36. package/template/src/components/create-post-trigger.tsx +44 -0
  37. package/template/src/components/device-profile-provider.tsx +309 -0
  38. package/template/src/components/header.tsx +95 -0
  39. package/template/src/components/loader.tsx +10 -0
  40. package/template/src/components/profile-menu.tsx +89 -0
  41. package/template/src/components/ui/components/alert-dialog.tsx +185 -0
  42. package/template/src/components/ui/components/attachment.tsx +207 -0
  43. package/template/src/components/ui/components/avatar.tsx +107 -0
  44. package/template/src/components/ui/components/badge.tsx +52 -0
  45. package/template/src/components/ui/components/bubble.tsx +128 -0
  46. package/template/src/components/ui/components/button-group.tsx +87 -0
  47. package/template/src/components/ui/components/button.tsx +56 -0
  48. package/template/src/components/ui/components/card.tsx +103 -0
  49. package/template/src/components/ui/components/checkbox.tsx +26 -0
  50. package/template/src/components/ui/components/command.tsx +194 -0
  51. package/template/src/components/ui/components/dialog.tsx +154 -0
  52. package/template/src/components/ui/components/drawer.tsx +229 -0
  53. package/template/src/components/ui/components/dropdown-menu.tsx +270 -0
  54. package/template/src/components/ui/components/empty.tsx +101 -0
  55. package/template/src/components/ui/components/field.tsx +236 -0
  56. package/template/src/components/ui/components/input-group.tsx +155 -0
  57. package/template/src/components/ui/components/input.tsx +20 -0
  58. package/template/src/components/ui/components/item.tsx +201 -0
  59. package/template/src/components/ui/components/kbd.tsx +26 -0
  60. package/template/src/components/ui/components/label.tsx +18 -0
  61. package/template/src/components/ui/components/marker.tsx +71 -0
  62. package/template/src/components/ui/components/message-scroller.tsx +128 -0
  63. package/template/src/components/ui/components/message.tsx +92 -0
  64. package/template/src/components/ui/components/native-select.tsx +65 -0
  65. package/template/src/components/ui/components/popover.tsx +88 -0
  66. package/template/src/components/ui/components/portal-primitives.test.tsx +85 -0
  67. package/template/src/components/ui/components/radio-group.tsx +36 -0
  68. package/template/src/components/ui/components/scroll-area.tsx +52 -0
  69. package/template/src/components/ui/components/secondary-form-actions.test.tsx +23 -0
  70. package/template/src/components/ui/components/select.tsx +199 -0
  71. package/template/src/components/ui/components/separator.tsx +23 -0
  72. package/template/src/components/ui/components/sheet.tsx +135 -0
  73. package/template/src/components/ui/components/skeleton.tsx +13 -0
  74. package/template/src/components/ui/components/sonner.tsx +43 -0
  75. package/template/src/components/ui/components/spinner.tsx +16 -0
  76. package/template/src/components/ui/components/tabs.tsx +80 -0
  77. package/template/src/components/ui/components/textarea.tsx +18 -0
  78. package/template/src/components/ui/components/tooltip.tsx +64 -0
  79. package/template/src/components/ui/hooks/.gitkeep +0 -0
  80. package/template/src/components/ui/lib/utils.ts +6 -0
  81. package/template/src/components/ui/styles/globals.css +151 -0
  82. package/template/src/create-post-overlay.tsx +82 -0
  83. package/template/src/draft-post-list.tsx +39 -0
  84. package/template/src/feedback-cache-invalidation.test.ts +185 -0
  85. package/template/src/feedback-cache-invalidation.ts +97 -0
  86. package/template/src/feedback-runtime.ts +38 -0
  87. package/template/src/feedbax/changelog/index.test.ts +156 -0
  88. package/template/src/feedbax/changelog/index.ts +206 -0
  89. package/template/src/feedbax/changelog/notion-changelog.test.ts +395 -0
  90. package/template/src/feedbax/changelog/notion-changelog.ts +400 -0
  91. package/template/src/feedbax/config/index.test.ts +111 -0
  92. package/template/src/feedbax/config/index.ts +271 -0
  93. package/template/src/feedbax/feedback/feedback-storage.ts +43 -0
  94. package/template/src/feedbax/feedback/index.test.ts +900 -0
  95. package/template/src/feedbax/feedback/index.ts +942 -0
  96. package/template/src/feedbax/feedback/notion-comments.test.ts +171 -0
  97. package/template/src/feedbax/feedback/notion-comments.ts +231 -0
  98. package/template/src/feedbax/feedback/notion-data-source.test.ts +364 -0
  99. package/template/src/feedbax/feedback/notion-data-source.ts +458 -0
  100. package/template/src/feedbax/feedback/notion-feedback.test.ts +998 -0
  101. package/template/src/feedbax/feedback/notion-feedback.ts +651 -0
  102. package/template/src/feedbax/feedback/notion-request.ts +74 -0
  103. package/template/src/feedbax.test-fixture.ts +64 -0
  104. package/template/src/feedbax.ts +69 -0
  105. package/template/src/index.css +2138 -0
  106. package/template/src/lazy-create-post-overlay.tsx +6 -0
  107. package/template/src/lazy-post-detail-overlay.tsx +10 -0
  108. package/template/src/masked-post-link.tsx +27 -0
  109. package/template/src/overlay-post-detail.test.tsx +130 -0
  110. package/template/src/overlay-post-detail.tsx +92 -0
  111. package/template/src/portal-comments.test.ts +375 -0
  112. package/template/src/portal-comments.ts +384 -0
  113. package/template/src/portal-features.test.tsx +150 -0
  114. package/template/src/portal-feedback-form.tsx +515 -0
  115. package/template/src/portal-feedback-runtime.test.ts +32 -0
  116. package/template/src/portal-feedback-runtime.ts +55 -0
  117. package/template/src/portal-feedback-server-function.ts +93 -0
  118. package/template/src/portal-feedback-submission.test.ts +307 -0
  119. package/template/src/portal-feedback-submission.ts +214 -0
  120. package/template/src/portal-head.ts +58 -0
  121. package/template/src/portal-server-entry.ts +64 -0
  122. package/template/src/portal-voting.test.ts +106 -0
  123. package/template/src/portal-voting.ts +149 -0
  124. package/template/src/post-badges.tsx +27 -0
  125. package/template/src/post-creation-flow.test.tsx +230 -0
  126. package/template/src/post-creation-flow.tsx +88 -0
  127. package/template/src/post-detail-overlay.test.tsx +114 -0
  128. package/template/src/post-detail-overlay.tsx +67 -0
  129. package/template/src/post-feed-controls.test.tsx +239 -0
  130. package/template/src/post-feed-controls.tsx +298 -0
  131. package/template/src/post-presentation.ts +37 -0
  132. package/template/src/post-queries.test.ts +158 -0
  133. package/template/src/post-queries.ts +26 -0
  134. package/template/src/post-results.tsx +165 -0
  135. package/template/src/public-cache-policy.test.ts +141 -0
  136. package/template/src/public-cache-policy.ts +119 -0
  137. package/template/src/public-cache-tags.ts +7 -0
  138. package/template/src/public-changelog-page.ts +15 -0
  139. package/template/src/public-changelog-server-function.ts +27 -0
  140. package/template/src/public-date.ts +11 -0
  141. package/template/src/public-failure-state.test.tsx +69 -0
  142. package/template/src/public-feedback-index.tsx +124 -0
  143. package/template/src/public-feedback-page.test.tsx +290 -0
  144. package/template/src/public-feedback-page.ts +75 -0
  145. package/template/src/public-feedback-server-function.ts +22 -0
  146. package/template/src/public-portal-error.tsx +27 -0
  147. package/template/src/public-post-detail.tsx +682 -0
  148. package/template/src/public-post-page.test.tsx +173 -0
  149. package/template/src/public-post-page.ts +15 -0
  150. package/template/src/public-post-route.test.tsx +214 -0
  151. package/template/src/public-post-route.tsx +171 -0
  152. package/template/src/public-post-server-function.ts +48 -0
  153. package/template/src/public-post-unavailable.tsx +10 -0
  154. package/template/src/public-roadmap-page.test.tsx +139 -0
  155. package/template/src/public-roadmap-page.ts +24 -0
  156. package/template/src/public-roadmap-route.test.tsx +218 -0
  157. package/template/src/public-roadmap-route.tsx +50 -0
  158. package/template/src/public-roadmap-server-function.ts +44 -0
  159. package/template/src/public-roadmap-view.tsx +378 -0
  160. package/template/src/public-shell.test.tsx +518 -0
  161. package/template/src/roadmap-query.ts +21 -0
  162. package/template/src/root-branding.test.ts +53 -0
  163. package/template/src/routeTree.gen.ts +131 -0
  164. package/template/src/routed-post-creation-form.tsx +28 -0
  165. package/template/src/router.tsx +25 -0
  166. package/template/src/routes/__root.tsx +116 -0
  167. package/template/src/routes/changelog.tsx +39 -0
  168. package/template/src/routes/index.tsx +61 -0
  169. package/template/src/routes/p.$slug.tsx +16 -0
  170. package/template/src/routes/roadmap.tsx +11 -0
  171. package/template/src/routes/submit.tsx +28 -0
  172. package/template/src/safe-public-failure.ts +45 -0
  173. package/template/src/server.ts +111 -0
  174. package/template/src/similar-posts.tsx +45 -0
  175. package/template/src/submit-route.test.tsx +134 -0
  176. package/template/src/trusted-feedback-handler.test.ts +425 -0
  177. package/template/src/trusted-feedback-handler.ts +181 -0
  178. package/template/src/trusted-feedback-runtime.ts +17 -0
  179. package/template/src/turnstile-configuration.test.ts +37 -0
  180. package/template/src/turnstile-configuration.ts +19 -0
  181. package/template/src/use-media-query.ts +17 -0
  182. package/template/src/vote-toggle.test.tsx +171 -0
  183. package/template/src/vote-toggle.tsx +211 -0
  184. package/template/tsconfig.json +45 -0
  185. package/template/vite.config.ts +20 -0
  186. package/template/vitest.config.ts +4 -0
  187. package/template/vitest.setup.ts +3 -0
  188. package/template/wrangler.jsonc +35 -0
  189. package/dist/cli.d.ts +0 -3
  190. package/dist/cli.d.ts.map +0 -1
  191. package/dist/cli.js.map +0 -1
  192. package/dist/index.d.ts +0 -21
  193. package/dist/index.d.ts.map +0 -1
  194. package/dist/index.js.map +0 -1
  195. package/template/.env.example +0 -4
  196. package/template/README.md +0 -7
  197. package/template/apps/portal/components.json +0 -20
  198. package/template/apps/portal/package.json +0 -31
  199. package/template/apps/portal/src/lib/utils.ts +0 -6
  200. package/template/apps/portal/src/routeTree.gen.ts +0 -59
  201. package/template/apps/portal/src/router.tsx +0 -10
  202. package/template/apps/portal/src/routes/__root.tsx +0 -31
  203. package/template/apps/portal/src/routes/index.tsx +0 -35
  204. package/template/apps/portal/src/styles.css +0 -91
  205. package/template/apps/portal/tsconfig.json +0 -15
  206. package/template/apps/portal/vite.config.ts +0 -13
  207. package/template/feedbax.config.ts +0 -13
  208. package/template/feedbax.jsonc +0 -11
  209. package/template/pnpm-workspace.yaml +0 -3
  210. package/template/turbo.json +0 -8
@@ -0,0 +1,199 @@
1
+ import * as React from "react";
2
+ import { Select as SelectPrimitive } from "@base-ui/react/select";
3
+ import { cn } from "@feedbax/ui/lib/utils";
4
+ import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
5
+
6
+ const Select = SelectPrimitive.Root;
7
+
8
+ function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
9
+ return (
10
+ <SelectPrimitive.Group
11
+ data-slot="select-group"
12
+ className={cn("scroll-my-1", className)}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
19
+ return (
20
+ <SelectPrimitive.Value
21
+ data-slot="select-value"
22
+ className={cn("flex flex-1 text-left", className)}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ function SelectTrigger({
29
+ className,
30
+ size = "default",
31
+ children,
32
+ ...props
33
+ }: SelectPrimitive.Trigger.Props & {
34
+ size?: "sm" | "default";
35
+ }) {
36
+ return (
37
+ <SelectPrimitive.Trigger
38
+ data-slot="select-trigger"
39
+ data-size={size}
40
+ className={cn(
41
+ "flex w-fit items-center justify-between gap-1.5 rounded-none border border-input bg-transparent py-2 pr-2 pl-2.5 text-xs whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-none *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 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",
42
+ className,
43
+ )}
44
+ {...props}
45
+ >
46
+ {children}
47
+ <SelectPrimitive.Icon
48
+ render={
49
+ <ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
50
+ }
51
+ />
52
+ </SelectPrimitive.Trigger>
53
+ );
54
+ }
55
+
56
+ function SelectContent({
57
+ className,
58
+ children,
59
+ side = "bottom",
60
+ sideOffset = 4,
61
+ align = "center",
62
+ alignOffset = 0,
63
+ alignItemWithTrigger = true,
64
+ ...props
65
+ }: SelectPrimitive.Popup.Props &
66
+ Pick<
67
+ SelectPrimitive.Positioner.Props,
68
+ "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
69
+ >) {
70
+ return (
71
+ <SelectPrimitive.Portal>
72
+ <SelectPrimitive.Positioner
73
+ side={side}
74
+ sideOffset={sideOffset}
75
+ align={align}
76
+ alignOffset={alignOffset}
77
+ alignItemWithTrigger={alignItemWithTrigger}
78
+ className="isolate z-50"
79
+ >
80
+ <SelectPrimitive.Popup
81
+ data-slot="select-content"
82
+ data-align-trigger={alignItemWithTrigger}
83
+ className={cn(
84
+ "relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-none bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
85
+ className,
86
+ )}
87
+ {...props}
88
+ >
89
+ <SelectScrollUpButton />
90
+ <SelectPrimitive.List>{children}</SelectPrimitive.List>
91
+ <SelectScrollDownButton />
92
+ </SelectPrimitive.Popup>
93
+ </SelectPrimitive.Positioner>
94
+ </SelectPrimitive.Portal>
95
+ );
96
+ }
97
+
98
+ function SelectLabel({
99
+ className,
100
+ ...props
101
+ }: SelectPrimitive.GroupLabel.Props) {
102
+ return (
103
+ <SelectPrimitive.GroupLabel
104
+ data-slot="select-label"
105
+ className={cn("px-2 py-2 text-xs text-muted-foreground", className)}
106
+ {...props}
107
+ />
108
+ );
109
+ }
110
+
111
+ function SelectItem({
112
+ className,
113
+ children,
114
+ ...props
115
+ }: SelectPrimitive.Item.Props) {
116
+ return (
117
+ <SelectPrimitive.Item
118
+ data-slot="select-item"
119
+ className={cn(
120
+ "relative flex w-full cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
121
+ className,
122
+ )}
123
+ {...props}
124
+ >
125
+ <SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
126
+ {children}
127
+ </SelectPrimitive.ItemText>
128
+ <SelectPrimitive.ItemIndicator
129
+ render={
130
+ <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
131
+ }
132
+ >
133
+ <CheckIcon className="pointer-events-none" />
134
+ </SelectPrimitive.ItemIndicator>
135
+ </SelectPrimitive.Item>
136
+ );
137
+ }
138
+
139
+ function SelectSeparator({
140
+ className,
141
+ ...props
142
+ }: SelectPrimitive.Separator.Props) {
143
+ return (
144
+ <SelectPrimitive.Separator
145
+ data-slot="select-separator"
146
+ className={cn("pointer-events-none -mx-1 h-px bg-border", className)}
147
+ {...props}
148
+ />
149
+ );
150
+ }
151
+
152
+ function SelectScrollUpButton({
153
+ className,
154
+ ...props
155
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
156
+ return (
157
+ <SelectPrimitive.ScrollUpArrow
158
+ data-slot="select-scroll-up-button"
159
+ className={cn(
160
+ "top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
161
+ className,
162
+ )}
163
+ {...props}
164
+ >
165
+ <ChevronUpIcon />
166
+ </SelectPrimitive.ScrollUpArrow>
167
+ );
168
+ }
169
+
170
+ function SelectScrollDownButton({
171
+ className,
172
+ ...props
173
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
174
+ return (
175
+ <SelectPrimitive.ScrollDownArrow
176
+ data-slot="select-scroll-down-button"
177
+ className={cn(
178
+ "bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
179
+ className,
180
+ )}
181
+ {...props}
182
+ >
183
+ <ChevronDownIcon />
184
+ </SelectPrimitive.ScrollDownArrow>
185
+ );
186
+ }
187
+
188
+ export {
189
+ Select,
190
+ SelectContent,
191
+ SelectGroup,
192
+ SelectItem,
193
+ SelectLabel,
194
+ SelectScrollDownButton,
195
+ SelectScrollUpButton,
196
+ SelectSeparator,
197
+ SelectTrigger,
198
+ SelectValue,
199
+ };
@@ -0,0 +1,23 @@
1
+ import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
2
+
3
+ import { cn } from "@feedbax/ui/lib/utils";
4
+
5
+ function Separator({
6
+ className,
7
+ orientation = "horizontal",
8
+ ...props
9
+ }: SeparatorPrimitive.Props) {
10
+ return (
11
+ <SeparatorPrimitive
12
+ data-slot="separator"
13
+ orientation={orientation}
14
+ className={cn(
15
+ "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ );
21
+ }
22
+
23
+ export { Separator };
@@ -0,0 +1,135 @@
1
+ import * as React from "react";
2
+ import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
3
+
4
+ import { cn } from "@feedbax/ui/lib/utils";
5
+ import { Button } from "@feedbax/ui/components/button";
6
+ import { XIcon } from "lucide-react";
7
+
8
+ function Sheet({ ...props }: SheetPrimitive.Root.Props) {
9
+ return <SheetPrimitive.Root data-slot="sheet" {...props} />;
10
+ }
11
+
12
+ function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
13
+ return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
14
+ }
15
+
16
+ function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
17
+ return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
18
+ }
19
+
20
+ function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
21
+ return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
22
+ }
23
+
24
+ function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
25
+ return (
26
+ <SheetPrimitive.Backdrop
27
+ data-slot="sheet-overlay"
28
+ className={cn(
29
+ "fixed inset-0 z-50 bg-black/10 text-xs/relaxed transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ function SheetContent({
38
+ className,
39
+ children,
40
+ side = "right",
41
+ showCloseButton = true,
42
+ ...props
43
+ }: SheetPrimitive.Popup.Props & {
44
+ side?: "top" | "right" | "bottom" | "left";
45
+ showCloseButton?: boolean;
46
+ }) {
47
+ return (
48
+ <SheetPortal>
49
+ <SheetOverlay />
50
+ <SheetPrimitive.Popup
51
+ data-slot="sheet-content"
52
+ data-side={side}
53
+ className={cn(
54
+ "fixed z-50 flex flex-col bg-popover bg-clip-padding text-xs/relaxed text-popover-foreground shadow-lg transition duration-200 ease-in-out data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] 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]:border-r data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] 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]:border-l data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
55
+ className,
56
+ )}
57
+ {...props}
58
+ >
59
+ {children}
60
+ {showCloseButton && (
61
+ <SheetPrimitive.Close
62
+ data-slot="sheet-close"
63
+ render={
64
+ <Button
65
+ variant="ghost"
66
+ className="absolute top-3 right-3"
67
+ size="icon-sm"
68
+ />
69
+ }
70
+ >
71
+ <XIcon />
72
+ <span className="sr-only">Close</span>
73
+ </SheetPrimitive.Close>
74
+ )}
75
+ </SheetPrimitive.Popup>
76
+ </SheetPortal>
77
+ );
78
+ }
79
+
80
+ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
81
+ return (
82
+ <div
83
+ data-slot="sheet-header"
84
+ className={cn("flex flex-col gap-0.5 p-4", className)}
85
+ {...props}
86
+ />
87
+ );
88
+ }
89
+
90
+ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
91
+ return (
92
+ <div
93
+ data-slot="sheet-footer"
94
+ className={cn("mt-auto flex flex-col gap-2 p-4", className)}
95
+ {...props}
96
+ />
97
+ );
98
+ }
99
+
100
+ function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
101
+ return (
102
+ <SheetPrimitive.Title
103
+ data-slot="sheet-title"
104
+ className={cn(
105
+ "font-heading text-sm font-medium text-foreground",
106
+ className,
107
+ )}
108
+ {...props}
109
+ />
110
+ );
111
+ }
112
+
113
+ function SheetDescription({
114
+ className,
115
+ ...props
116
+ }: SheetPrimitive.Description.Props) {
117
+ return (
118
+ <SheetPrimitive.Description
119
+ data-slot="sheet-description"
120
+ className={cn("text-xs/relaxed text-muted-foreground", className)}
121
+ {...props}
122
+ />
123
+ );
124
+ }
125
+
126
+ export {
127
+ Sheet,
128
+ SheetTrigger,
129
+ SheetClose,
130
+ SheetContent,
131
+ SheetHeader,
132
+ SheetFooter,
133
+ SheetTitle,
134
+ SheetDescription,
135
+ };
@@ -0,0 +1,13 @@
1
+ import { cn } from "@feedbax/ui/lib/utils";
2
+
3
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4
+ return (
5
+ <div
6
+ data-slot="skeleton"
7
+ className={cn("animate-pulse rounded-none bg-muted", className)}
8
+ {...props}
9
+ />
10
+ );
11
+ }
12
+
13
+ export { Skeleton };
@@ -0,0 +1,43 @@
1
+ import { useTheme } from "next-themes";
2
+ import { Toaster as Sonner, type ToasterProps } from "sonner";
3
+ import {
4
+ CircleCheckIcon,
5
+ InfoIcon,
6
+ TriangleAlertIcon,
7
+ OctagonXIcon,
8
+ Loader2Icon,
9
+ } from "lucide-react";
10
+
11
+ const Toaster = ({ ...props }: ToasterProps) => {
12
+ const { theme = "system" } = useTheme();
13
+
14
+ return (
15
+ <Sonner
16
+ theme={theme as ToasterProps["theme"]}
17
+ className="toaster group"
18
+ icons={{
19
+ success: <CircleCheckIcon className="size-4" />,
20
+ info: <InfoIcon className="size-4" />,
21
+ warning: <TriangleAlertIcon className="size-4" />,
22
+ error: <OctagonXIcon className="size-4" />,
23
+ loading: <Loader2Icon className="size-4 animate-spin" />,
24
+ }}
25
+ style={
26
+ {
27
+ "--normal-bg": "var(--popover)",
28
+ "--normal-text": "var(--popover-foreground)",
29
+ "--normal-border": "var(--border)",
30
+ "--border-radius": "var(--radius)",
31
+ } as React.CSSProperties
32
+ }
33
+ toastOptions={{
34
+ classNames: {
35
+ toast: "cn-toast",
36
+ },
37
+ }}
38
+ {...props}
39
+ />
40
+ );
41
+ };
42
+
43
+ export { Toaster };
@@ -0,0 +1,16 @@
1
+ import { cn } from "@feedbax/ui/lib/utils";
2
+ import { Loader2Icon } from "lucide-react";
3
+
4
+ function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
5
+ return (
6
+ <Loader2Icon
7
+ data-slot="spinner"
8
+ role="status"
9
+ aria-label="Loading"
10
+ className={cn("size-4 animate-spin", className)}
11
+ {...props}
12
+ />
13
+ );
14
+ }
15
+
16
+ export { Spinner };
@@ -0,0 +1,80 @@
1
+ import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "@feedbax/ui/lib/utils";
5
+
6
+ function Tabs({
7
+ className,
8
+ orientation = "horizontal",
9
+ ...props
10
+ }: TabsPrimitive.Root.Props) {
11
+ return (
12
+ <TabsPrimitive.Root
13
+ data-slot="tabs"
14
+ data-orientation={orientation}
15
+ className={cn(
16
+ "group/tabs flex gap-2 data-horizontal:flex-col",
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ const tabsListVariants = cva(
25
+ "group/tabs-list inline-flex w-fit items-center justify-center rounded-none p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
26
+ {
27
+ variants: {
28
+ variant: {
29
+ default: "bg-muted",
30
+ line: "gap-1 bg-transparent",
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ variant: "default",
35
+ },
36
+ },
37
+ );
38
+
39
+ function TabsList({
40
+ className,
41
+ variant = "default",
42
+ ...props
43
+ }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
44
+ return (
45
+ <TabsPrimitive.List
46
+ data-slot="tabs-list"
47
+ data-variant={variant}
48
+ className={cn(tabsListVariants({ variant }), className)}
49
+ {...props}
50
+ />
51
+ );
52
+ }
53
+
54
+ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
55
+ return (
56
+ <TabsPrimitive.Tab
57
+ data-slot="tabs-trigger"
58
+ className={cn(
59
+ "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-none border border-transparent px-1.5 py-0.5 text-xs font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start group-data-vertical/tabs:py-[calc(--spacing(1.25))] hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
60
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
61
+ "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
62
+ "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
63
+ className,
64
+ )}
65
+ {...props}
66
+ />
67
+ );
68
+ }
69
+
70
+ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
71
+ return (
72
+ <TabsPrimitive.Panel
73
+ data-slot="tabs-content"
74
+ className={cn("flex-1 text-xs/relaxed outline-none", className)}
75
+ {...props}
76
+ />
77
+ );
78
+ }
79
+
80
+ export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@feedbax/ui/lib/utils";
4
+
5
+ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6
+ return (
7
+ <textarea
8
+ data-slot="textarea"
9
+ className={cn(
10
+ "flex field-sizing-content min-h-16 w-full rounded-none border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-xs dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ export { Textarea };
@@ -0,0 +1,64 @@
1
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
2
+
3
+ import { cn } from "@feedbax/ui/lib/utils";
4
+
5
+ function TooltipProvider({
6
+ delay = 0,
7
+ ...props
8
+ }: TooltipPrimitive.Provider.Props) {
9
+ return (
10
+ <TooltipPrimitive.Provider
11
+ data-slot="tooltip-provider"
12
+ delay={delay}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
19
+ return <TooltipPrimitive.Root data-slot="tooltip" {...props} />;
20
+ }
21
+
22
+ function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
23
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
24
+ }
25
+
26
+ function TooltipContent({
27
+ className,
28
+ side = "top",
29
+ sideOffset = 4,
30
+ align = "center",
31
+ alignOffset = 0,
32
+ children,
33
+ ...props
34
+ }: TooltipPrimitive.Popup.Props &
35
+ Pick<
36
+ TooltipPrimitive.Positioner.Props,
37
+ "align" | "alignOffset" | "side" | "sideOffset"
38
+ >) {
39
+ return (
40
+ <TooltipPrimitive.Portal>
41
+ <TooltipPrimitive.Positioner
42
+ align={align}
43
+ alignOffset={alignOffset}
44
+ side={side}
45
+ sideOffset={sideOffset}
46
+ className="isolate z-50"
47
+ >
48
+ <TooltipPrimitive.Popup
49
+ data-slot="tooltip-content"
50
+ className={cn(
51
+ "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-none bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-none data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
52
+ className,
53
+ )}
54
+ {...props}
55
+ >
56
+ {children}
57
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-none bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
58
+ </TooltipPrimitive.Popup>
59
+ </TooltipPrimitive.Positioner>
60
+ </TooltipPrimitive.Portal>
61
+ );
62
+ }
63
+
64
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
File without changes
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }