rusty-replay 0.0.4

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 (141) hide show
  1. package/.eslintrc.js +10 -0
  2. package/.vscode/settings.json +3 -0
  3. package/README.md +92 -0
  4. package/apps/web/README.md +11 -0
  5. package/apps/web/api/auth/keys.ts +3 -0
  6. package/apps/web/api/auth/types.ts +25 -0
  7. package/apps/web/api/auth/use-query-profile.ts +19 -0
  8. package/apps/web/api/auth/use-sign-in.ts +24 -0
  9. package/apps/web/api/axios.ts +122 -0
  10. package/apps/web/api/error-code.ts +36 -0
  11. package/apps/web/api/event/keys.ts +14 -0
  12. package/apps/web/api/event/types.ts +91 -0
  13. package/apps/web/api/event/use-mutation-event-assignee.ts +103 -0
  14. package/apps/web/api/event/use-mutation-event-priority.ts +97 -0
  15. package/apps/web/api/event/use-mutation-event-status.ts +198 -0
  16. package/apps/web/api/event/use-query-event-detail.ts +25 -0
  17. package/apps/web/api/event/use-query-event-list.ts +42 -0
  18. package/apps/web/api/health-check/index.ts +21 -0
  19. package/apps/web/api/project/keys.ts +4 -0
  20. package/apps/web/api/project/types.ts +28 -0
  21. package/apps/web/api/project/use-create-project.ts +30 -0
  22. package/apps/web/api/project/use-query-project-list.ts +19 -0
  23. package/apps/web/api/project/use-query-project-users.ts +23 -0
  24. package/apps/web/api/types.ts +44 -0
  25. package/apps/web/app/(auth)/layout.tsx +5 -0
  26. package/apps/web/app/(auth)/sign-in/page.tsx +20 -0
  27. package/apps/web/app/(auth)/sign-up/page.tsx +5 -0
  28. package/apps/web/app/(project)/project/[project_id]/issues/[issue_id]/page.tsx +17 -0
  29. package/apps/web/app/(project)/project/[project_id]/issues/page.tsx +15 -0
  30. package/apps/web/app/(project)/project/[project_id]/page.tsx +10 -0
  31. package/apps/web/app/(project)/project/page.tsx +10 -0
  32. package/apps/web/app/(report)/error-list/page.tsx +7 -0
  33. package/apps/web/app/favicon.ico +0 -0
  34. package/apps/web/app/layout.tsx +35 -0
  35. package/apps/web/app/page.tsx +3 -0
  36. package/apps/web/components/.gitkeep +0 -0
  37. package/apps/web/components/event-list/event-detail.tsx +242 -0
  38. package/apps/web/components/event-list/event-list.tsx +376 -0
  39. package/apps/web/components/event-list/preview.tsx +573 -0
  40. package/apps/web/components/layouts/default-layout.tsx +59 -0
  41. package/apps/web/components/login-form.tsx +124 -0
  42. package/apps/web/components/project/create-project.tsx +130 -0
  43. package/apps/web/components/project/hooks/use-get-event-params.ts +9 -0
  44. package/apps/web/components/project/hooks/use-get-project-params.ts +10 -0
  45. package/apps/web/components/project/project-detail.tsx +240 -0
  46. package/apps/web/components/project/project-list.tsx +137 -0
  47. package/apps/web/components/providers.tsx +25 -0
  48. package/apps/web/components/ui/assignee-dropdown.tsx +176 -0
  49. package/apps/web/components/ui/event-status-dropdown.tsx +104 -0
  50. package/apps/web/components/ui/priority-dropdown.tsx +123 -0
  51. package/apps/web/components/widget/app-sidebar.tsx +225 -0
  52. package/apps/web/components/widget/nav-main.tsx +73 -0
  53. package/apps/web/components/widget/nav-projects.tsx +84 -0
  54. package/apps/web/components/widget/nav-user.tsx +113 -0
  55. package/apps/web/components.json +20 -0
  56. package/apps/web/constants/routes.ts +12 -0
  57. package/apps/web/eslint.config.js +4 -0
  58. package/apps/web/hooks/use-boolean-state.ts +13 -0
  59. package/apps/web/lib/.gitkeep +0 -0
  60. package/apps/web/next-env.d.ts +5 -0
  61. package/apps/web/next.config.mjs +6 -0
  62. package/apps/web/package.json +60 -0
  63. package/apps/web/postcss.config.mjs +1 -0
  64. package/apps/web/providers/flag-provider.tsx +35 -0
  65. package/apps/web/providers/query-client-provider.tsx +17 -0
  66. package/apps/web/providers/telemetry-provider.tsx +12 -0
  67. package/apps/web/tsconfig.json +24 -0
  68. package/apps/web/utils/avatar.ts +26 -0
  69. package/apps/web/utils/date.ts +26 -0
  70. package/apps/web/utils/front-end-tracer.ts +119 -0
  71. package/apps/web/utils/schema/project.schema.ts +12 -0
  72. package/apps/web/utils/span-processor.ts +36 -0
  73. package/package.json +21 -0
  74. package/packages/eslint-config/README.md +3 -0
  75. package/packages/eslint-config/base.js +32 -0
  76. package/packages/eslint-config/next.js +51 -0
  77. package/packages/eslint-config/package.json +25 -0
  78. package/packages/eslint-config/react-internal.js +41 -0
  79. package/packages/rusty-replay/README.md +165 -0
  80. package/packages/rusty-replay/package.json +67 -0
  81. package/packages/rusty-replay/src/environment.ts +27 -0
  82. package/packages/rusty-replay/src/error-batcher.ts +75 -0
  83. package/packages/rusty-replay/src/front-end-tracer.ts +86 -0
  84. package/packages/rusty-replay/src/handler.ts +37 -0
  85. package/packages/rusty-replay/src/index.ts +8 -0
  86. package/packages/rusty-replay/src/recorder.ts +71 -0
  87. package/packages/rusty-replay/src/reporter.ts +115 -0
  88. package/packages/rusty-replay/src/utils.ts +13 -0
  89. package/packages/rusty-replay/tsconfig.build.json +13 -0
  90. package/packages/rusty-replay/tsconfig.json +27 -0
  91. package/packages/rusty-replay/tsup.config.ts +39 -0
  92. package/packages/typescript-config/README.md +3 -0
  93. package/packages/typescript-config/base.json +20 -0
  94. package/packages/typescript-config/nextjs.json +13 -0
  95. package/packages/typescript-config/package.json +9 -0
  96. package/packages/typescript-config/react-library.json +8 -0
  97. package/packages/ui/components.json +20 -0
  98. package/packages/ui/eslint.config.js +4 -0
  99. package/packages/ui/package.json +60 -0
  100. package/packages/ui/postcss.config.mjs +6 -0
  101. package/packages/ui/src/components/.gitkeep +0 -0
  102. package/packages/ui/src/components/avatar.tsx +53 -0
  103. package/packages/ui/src/components/badge.tsx +46 -0
  104. package/packages/ui/src/components/breadcrumb.tsx +109 -0
  105. package/packages/ui/src/components/button.tsx +59 -0
  106. package/packages/ui/src/components/calendar.tsx +75 -0
  107. package/packages/ui/src/components/calendars/date-picker.tsx +43 -0
  108. package/packages/ui/src/components/calendars/date-range-picker.tsx +79 -0
  109. package/packages/ui/src/components/card.tsx +92 -0
  110. package/packages/ui/src/components/checkbox.tsx +32 -0
  111. package/packages/ui/src/components/collapsible.tsx +33 -0
  112. package/packages/ui/src/components/dialog.tsx +135 -0
  113. package/packages/ui/src/components/dialogs/confirmation-modal.tsx +216 -0
  114. package/packages/ui/src/components/dropdown-menu.tsx +261 -0
  115. package/packages/ui/src/components/input.tsx +30 -0
  116. package/packages/ui/src/components/label.tsx +24 -0
  117. package/packages/ui/src/components/login-form.tsx +68 -0
  118. package/packages/ui/src/components/mode-switcher.tsx +34 -0
  119. package/packages/ui/src/components/popover.tsx +48 -0
  120. package/packages/ui/src/components/scroll-area.tsx +58 -0
  121. package/packages/ui/src/components/select.tsx +185 -0
  122. package/packages/ui/src/components/separator.tsx +28 -0
  123. package/packages/ui/src/components/sheet.tsx +139 -0
  124. package/packages/ui/src/components/sidebar.tsx +726 -0
  125. package/packages/ui/src/components/skeleton.tsx +13 -0
  126. package/packages/ui/src/components/sonner.tsx +25 -0
  127. package/packages/ui/src/components/table.tsx +116 -0
  128. package/packages/ui/src/components/tabs.tsx +66 -0
  129. package/packages/ui/src/components/team-switcher.tsx +91 -0
  130. package/packages/ui/src/components/textarea.tsx +18 -0
  131. package/packages/ui/src/components/tooltip.tsx +61 -0
  132. package/packages/ui/src/hooks/.gitkeep +0 -0
  133. package/packages/ui/src/hooks/use-meta-color.ts +28 -0
  134. package/packages/ui/src/hooks/use-mobile.ts +19 -0
  135. package/packages/ui/src/lib/utils.ts +6 -0
  136. package/packages/ui/src/styles/globals.css +138 -0
  137. package/packages/ui/tsconfig.json +13 -0
  138. package/packages/ui/tsconfig.lint.json +8 -0
  139. package/pnpm-workspace.yaml +4 -0
  140. package/tsconfig.json +4 -0
  141. package/turbo.json +21 -0
@@ -0,0 +1,216 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { XIcon } from 'lucide-react';
5
+
6
+ import { cn } from '@workspace/ui/lib/utils';
7
+ import { Button } from '../button';
8
+ import {
9
+ Dialog,
10
+ DialogClose,
11
+ DialogContent,
12
+ DialogFooter,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from '../dialog';
16
+
17
+ export interface ConfirmationModalProps {
18
+ /**
19
+ * Whether the modal is visible
20
+ */
21
+ visible: boolean;
22
+ /**
23
+ * The title of the modal
24
+ */
25
+ title: string | React.ReactNode;
26
+ /**
27
+ * The size of the modal
28
+ */
29
+ size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
30
+ /**
31
+ * The label for the confirm button
32
+ */
33
+ confirmLabel?: string;
34
+ /**
35
+ * The label for the confirm button when loading
36
+ */
37
+ confirmLabelLoading?: string;
38
+ /**
39
+ * The label for the cancel button
40
+ */
41
+ cancelLabel?: string;
42
+ /**
43
+ * Callback when the confirm button is clicked
44
+ */
45
+ onConfirm: () => void;
46
+ /**
47
+ * Callback when the modal is closed or cancel button is clicked
48
+ */
49
+ onCancel: () => void;
50
+ /**
51
+ * Whether the modal is in a loading state
52
+ */
53
+ loading?: boolean;
54
+ /**
55
+ * Whether the confirm button is disabled
56
+ */
57
+ disabled?: boolean;
58
+ /**
59
+ * The variant of the confirm button
60
+ */
61
+ variant?: 'default' | 'destructive' | 'warning';
62
+ /**
63
+ * Alert configuration
64
+ */
65
+ alert?: {
66
+ title?: string;
67
+ description?: string | React.ReactNode;
68
+ className?: string;
69
+ };
70
+ /**
71
+ * Whether to hide the close button
72
+ */
73
+ hideClose?: boolean;
74
+ /**
75
+ * Children to render in the modal body
76
+ */
77
+ children?: React.ReactNode;
78
+ /**
79
+ * Additional class names for the modal content
80
+ */
81
+ className?: string;
82
+ }
83
+
84
+ const sizeMap = {
85
+ sm: 'sm:max-w-sm',
86
+ md: 'sm:max-w-md',
87
+ lg: 'sm:max-w-lg',
88
+ xl: 'sm:max-w-xl',
89
+ full: 'sm:max-w-[calc(100%-4rem)]',
90
+ };
91
+
92
+ const buttonVariantMap = {
93
+ default: 'default',
94
+ destructive: 'destructive',
95
+ warning: 'warning',
96
+ };
97
+
98
+ const ConfirmationModal = React.forwardRef<
99
+ React.ElementRef<typeof DialogContent>,
100
+ ConfirmationModalProps
101
+ >(
102
+ (
103
+ {
104
+ title,
105
+ size = 'sm',
106
+ visible,
107
+ onCancel,
108
+ onConfirm,
109
+ loading = false,
110
+ cancelLabel = 'Cancel',
111
+ confirmLabel = 'Submit',
112
+ confirmLabelLoading,
113
+ alert,
114
+ children,
115
+ variant = 'default',
116
+ disabled,
117
+ hideClose = false,
118
+ className,
119
+ ...props
120
+ },
121
+ ref
122
+ ) => {
123
+ // Handle the button click
124
+ const handleConfirm: React.MouseEventHandler<HTMLButtonElement> = (e) => {
125
+ e.preventDefault();
126
+ e.stopPropagation();
127
+ onConfirm();
128
+ };
129
+
130
+ return (
131
+ <Dialog
132
+ open={visible}
133
+ onOpenChange={(open) => {
134
+ if (!open && visible) {
135
+ onCancel();
136
+ }
137
+ }}
138
+ >
139
+ <DialogContent
140
+ ref={ref}
141
+ className={cn(
142
+ 'p-0 gap-0 block overflow-hidden',
143
+ sizeMap[size],
144
+ className
145
+ )}
146
+ {...props}
147
+ >
148
+ {!hideClose && (
149
+ <DialogClose className="absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
150
+ <XIcon className="h-4 w-4" />
151
+ <span className="sr-only">Close</span>
152
+ </DialogClose>
153
+ )}
154
+
155
+ <DialogHeader className="border-b p-4">
156
+ <DialogTitle className="text-lg font-semibold">{title}</DialogTitle>
157
+ </DialogHeader>
158
+
159
+ {alert && (
160
+ <div
161
+ className={cn(
162
+ 'p-4 border-b',
163
+ {
164
+ 'bg-red-50 text-red-900': variant === 'destructive',
165
+ 'bg-yellow-50 text-yellow-900': variant === 'warning',
166
+ 'bg-blue-50 text-blue-900': variant === 'default',
167
+ },
168
+ alert.className
169
+ )}
170
+ >
171
+ {alert.title && <div className="font-medium">{alert.title}</div>}
172
+ {alert.description && (
173
+ <div className="text-sm mt-1">{alert.description}</div>
174
+ )}
175
+ </div>
176
+ )}
177
+
178
+ {children && <div className="p-4">{children}</div>}
179
+
180
+ <DialogFooter className="flex justify-end gap-2 border-t p-4">
181
+ <Button
182
+ variant="outline"
183
+ size="sm"
184
+ onClick={onCancel}
185
+ disabled={loading}
186
+ >
187
+ {cancelLabel}
188
+ </Button>
189
+ <Button
190
+ variant={
191
+ buttonVariantMap[variant] as
192
+ | 'default'
193
+ | 'destructive'
194
+ | 'secondary'
195
+ | 'outline'
196
+ | 'ghost'
197
+ | 'link'
198
+ | undefined
199
+ }
200
+ size="sm"
201
+ onClick={handleConfirm}
202
+ disabled={disabled || loading}
203
+ className="truncate"
204
+ >
205
+ {loading ? confirmLabelLoading || confirmLabel : confirmLabel}
206
+ </Button>
207
+ </DialogFooter>
208
+ </DialogContent>
209
+ </Dialog>
210
+ );
211
+ }
212
+ );
213
+
214
+ ConfirmationModal.displayName = 'ConfirmationModal';
215
+
216
+ export { ConfirmationModal };
@@ -0,0 +1,261 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
5
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';
6
+
7
+ import { cn } from '@workspace/ui/lib/utils';
8
+
9
+ function DropdownMenu({
10
+ ...props
11
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
12
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
13
+ }
14
+
15
+ function DropdownMenuPortal({
16
+ ...props
17
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
18
+ return (
19
+ <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
20
+ );
21
+ }
22
+
23
+ function DropdownMenuTrigger({
24
+ ...props
25
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
26
+ return (
27
+ <DropdownMenuPrimitive.Trigger
28
+ data-slot="dropdown-menu-trigger"
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ function DropdownMenuContent({
35
+ className,
36
+ sideOffset = 4,
37
+ ...props
38
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
39
+ return (
40
+ <DropdownMenuPrimitive.Portal>
41
+ <DropdownMenuPrimitive.Content
42
+ data-slot="dropdown-menu-content"
43
+ sideOffset={sideOffset}
44
+ className={cn(
45
+ 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ </DropdownMenuPrimitive.Portal>
51
+ );
52
+ }
53
+
54
+ function DropdownMenuGroup({
55
+ ...props
56
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
57
+ return (
58
+ <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
59
+ );
60
+ }
61
+
62
+ function DropdownMenuItem({
63
+ className,
64
+ inset,
65
+ variant = 'default',
66
+ ...props
67
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
68
+ inset?: boolean;
69
+ variant?: 'default' | 'destructive';
70
+ }) {
71
+ return (
72
+ <DropdownMenuPrimitive.Item
73
+ data-slot="dropdown-menu-item"
74
+ data-inset={inset}
75
+ data-variant={variant}
76
+ className={cn(
77
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
78
+ className
79
+ )}
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+
85
+ function DropdownMenuCheckboxItem({
86
+ className,
87
+ children,
88
+ checked,
89
+ ...props
90
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
91
+ return (
92
+ <DropdownMenuPrimitive.CheckboxItem
93
+ data-slot="dropdown-menu-checkbox-item"
94
+ className={cn(
95
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
96
+ className
97
+ )}
98
+ checked={checked}
99
+ {...props}
100
+ >
101
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
102
+ <DropdownMenuPrimitive.ItemIndicator>
103
+ <CheckIcon className="size-4" />
104
+ </DropdownMenuPrimitive.ItemIndicator>
105
+ </span>
106
+ {children}
107
+ </DropdownMenuPrimitive.CheckboxItem>
108
+ );
109
+ }
110
+
111
+ function DropdownMenuRadioGroup({
112
+ ...props
113
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
114
+ return (
115
+ <DropdownMenuPrimitive.RadioGroup
116
+ data-slot="dropdown-menu-radio-group"
117
+ {...props}
118
+ />
119
+ );
120
+ }
121
+
122
+ function DropdownMenuRadioItem({
123
+ className,
124
+ children,
125
+ ...props
126
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
127
+ return (
128
+ <DropdownMenuPrimitive.RadioItem
129
+ data-slot="dropdown-menu-radio-item"
130
+ className={cn(
131
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
132
+ className
133
+ )}
134
+ {...props}
135
+ >
136
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
137
+ <DropdownMenuPrimitive.ItemIndicator>
138
+ <CircleIcon className="size-2 fill-current" />
139
+ </DropdownMenuPrimitive.ItemIndicator>
140
+ </span>
141
+ {children}
142
+ </DropdownMenuPrimitive.RadioItem>
143
+ );
144
+ }
145
+
146
+ function DropdownMenuLabel({
147
+ className,
148
+ inset,
149
+ ...props
150
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151
+ inset?: boolean;
152
+ }) {
153
+ return (
154
+ <DropdownMenuPrimitive.Label
155
+ data-slot="dropdown-menu-label"
156
+ data-inset={inset}
157
+ className={cn(
158
+ 'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
159
+ className
160
+ )}
161
+ {...props}
162
+ />
163
+ );
164
+ }
165
+
166
+ function DropdownMenuSeparator({
167
+ className,
168
+ ...props
169
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
170
+ return (
171
+ <DropdownMenuPrimitive.Separator
172
+ data-slot="dropdown-menu-separator"
173
+ className={cn('bg-border -mx-1 my-1 h-px', className)}
174
+ {...props}
175
+ />
176
+ );
177
+ }
178
+
179
+ function DropdownMenuShortcut({
180
+ className,
181
+ ...props
182
+ }: React.ComponentProps<'span'>) {
183
+ return (
184
+ <span
185
+ data-slot="dropdown-menu-shortcut"
186
+ className={cn(
187
+ 'text-muted-foreground ml-auto text-xs tracking-widest',
188
+ className
189
+ )}
190
+ {...props}
191
+ />
192
+ );
193
+ }
194
+
195
+ function DropdownMenuSub({
196
+ ...props
197
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
199
+ }
200
+
201
+ function DropdownMenuSubTrigger({
202
+ className,
203
+ inset,
204
+ children,
205
+ ...props
206
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207
+ inset?: boolean;
208
+ }) {
209
+ return (
210
+ <DropdownMenuPrimitive.SubTrigger
211
+ data-slot="dropdown-menu-sub-trigger"
212
+ data-inset={inset}
213
+ className={cn(
214
+ 'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
215
+ className
216
+ )}
217
+ {...props}
218
+ >
219
+ {children}
220
+ <ChevronRightIcon className="ml-auto size-4" />
221
+ </DropdownMenuPrimitive.SubTrigger>
222
+ );
223
+ }
224
+
225
+ function DropdownMenuSubContent({
226
+ className,
227
+ ...props
228
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
229
+ return (
230
+ <DropdownMenuPrimitive.SubContent
231
+ data-slot="dropdown-menu-sub-content"
232
+ className={cn(
233
+ 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
234
+ className
235
+ )}
236
+ {...props}
237
+ />
238
+ );
239
+ }
240
+
241
+ import { DropdownMenuCheckboxItemProps } from '@radix-ui/react-dropdown-menu';
242
+
243
+ export {
244
+ DropdownMenu,
245
+ DropdownMenuPortal,
246
+ DropdownMenuTrigger,
247
+ DropdownMenuContent,
248
+ DropdownMenuGroup,
249
+ DropdownMenuLabel,
250
+ DropdownMenuItem,
251
+ DropdownMenuCheckboxItem,
252
+ DropdownMenuRadioGroup,
253
+ DropdownMenuRadioItem,
254
+ DropdownMenuSeparator,
255
+ DropdownMenuShortcut,
256
+ DropdownMenuSub,
257
+ DropdownMenuSubTrigger,
258
+ DropdownMenuSubContent,
259
+ };
260
+
261
+ export type { DropdownMenuCheckboxItemProps };
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '@workspace/ui/lib/utils';
4
+
5
+ interface InputProps extends React.ComponentProps<'input'> {
6
+ error?: string;
7
+ }
8
+
9
+ function Input({ className, type, error, ...props }: InputProps) {
10
+ return (
11
+ <div className="w-full">
12
+ <input
13
+ type={type}
14
+ data-slot="input"
15
+ className={cn(
16
+ 'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
17
+ 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
18
+ 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
19
+ error ? 'border-destructive' : '',
20
+ className
21
+ )}
22
+ aria-invalid={error ? 'true' : 'false'}
23
+ {...props}
24
+ />
25
+ {error && <p className="text-xs text-destructive mt-1">{error}</p>}
26
+ </div>
27
+ );
28
+ }
29
+
30
+ export { Input };
@@ -0,0 +1,24 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+
6
+ import { cn } from "@workspace/ui/lib/utils"
7
+
8
+ function Label({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
12
+ return (
13
+ <LabelPrimitive.Root
14
+ data-slot="label"
15
+ className={cn(
16
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ export { Label }
@@ -0,0 +1,68 @@
1
+ import { cn } from '@workspace/ui/lib/utils';
2
+ import { Button } from '@workspace/ui/components/button';
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardHeader,
8
+ CardTitle,
9
+ } from '@workspace/ui/components/card';
10
+ import { Input } from '@workspace/ui/components/input';
11
+ import { Label } from '@workspace/ui/components/label';
12
+
13
+ export function LoginForm({
14
+ className,
15
+ ...props
16
+ }: React.ComponentPropsWithoutRef<'div'>) {
17
+ return (
18
+ <div className={cn('flex flex-col gap-6', className)} {...props}>
19
+ <Card>
20
+ <CardHeader>
21
+ <CardTitle className="text-2xl">Login</CardTitle>
22
+ <CardDescription>
23
+ Enter your email below to login to your account
24
+ </CardDescription>
25
+ </CardHeader>
26
+ <CardContent>
27
+ <form>
28
+ <div className="flex flex-col gap-6">
29
+ <div className="grid gap-2">
30
+ <Label htmlFor="email">Email</Label>
31
+ <Input
32
+ id="email"
33
+ type="email"
34
+ placeholder="m@example.com"
35
+ required
36
+ />
37
+ </div>
38
+ <div className="grid gap-2">
39
+ <div className="flex items-center">
40
+ <Label htmlFor="password">Password</Label>
41
+ <a
42
+ href="#"
43
+ className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
44
+ >
45
+ Forgot your password?
46
+ </a>
47
+ </div>
48
+ <Input id="password" type="password" required />
49
+ </div>
50
+ <Button type="submit" className="w-full">
51
+ Login
52
+ </Button>
53
+ <Button variant="outline" className="w-full">
54
+ Login with Google
55
+ </Button>
56
+ </div>
57
+ <div className="mt-4 text-center text-sm">
58
+ Don&apos;t have an account?{' '}
59
+ <a href="#" className="underline underline-offset-4">
60
+ Sign up
61
+ </a>
62
+ </div>
63
+ </form>
64
+ </CardContent>
65
+ </Card>
66
+ </div>
67
+ );
68
+ }
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { MoonIcon, SunIcon } from 'lucide-react';
5
+ import { useTheme } from 'next-themes';
6
+ import { Button } from './button';
7
+ import { META_THEME_COLORS, useMetaColor } from '../hooks/use-meta-color';
8
+
9
+ export function ModeSwitcher() {
10
+ const { setTheme, resolvedTheme } = useTheme();
11
+ const { setMetaColor } = useMetaColor();
12
+
13
+ const toggleTheme = React.useCallback(() => {
14
+ setTheme(resolvedTheme === 'dark' ? 'light' : 'dark');
15
+ setMetaColor(
16
+ resolvedTheme === 'dark'
17
+ ? META_THEME_COLORS.light
18
+ : META_THEME_COLORS.dark
19
+ );
20
+ }, [resolvedTheme, setTheme, setMetaColor]);
21
+
22
+ return (
23
+ <Button
24
+ variant="outline"
25
+ size="icon"
26
+ className="group/toggle size-8"
27
+ onClick={toggleTheme}
28
+ >
29
+ <SunIcon className="hidden [html.dark_&]:block" />
30
+ <MoonIcon className="hidden [html.light_&]:block" />
31
+ <span className="sr-only">Toggle theme</span>
32
+ </Button>
33
+ );
34
+ }
@@ -0,0 +1,48 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as PopoverPrimitive from "@radix-ui/react-popover"
5
+
6
+ import { cn } from "@workspace/ui/lib/utils"
7
+
8
+ function Popover({
9
+ ...props
10
+ }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
11
+ return <PopoverPrimitive.Root data-slot="popover" {...props} />
12
+ }
13
+
14
+ function PopoverTrigger({
15
+ ...props
16
+ }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
17
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
18
+ }
19
+
20
+ function PopoverContent({
21
+ className,
22
+ align = "center",
23
+ sideOffset = 4,
24
+ ...props
25
+ }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
26
+ return (
27
+ <PopoverPrimitive.Portal>
28
+ <PopoverPrimitive.Content
29
+ data-slot="popover-content"
30
+ align={align}
31
+ sideOffset={sideOffset}
32
+ className={cn(
33
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
34
+ className
35
+ )}
36
+ {...props}
37
+ />
38
+ </PopoverPrimitive.Portal>
39
+ )
40
+ }
41
+
42
+ function PopoverAnchor({
43
+ ...props
44
+ }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
45
+ return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
46
+ }
47
+
48
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }