@srcroot/ui 0.0.43 → 0.0.45

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.
@@ -126,54 +126,52 @@
126
126
  }
127
127
  }
128
128
 
129
- @media (prefers-color-scheme: dark) {
130
- :root {
131
- --background: hsl(222.2 84% 4.9%);
132
- --foreground: hsl(210 40% 98%);
129
+ .dark {
130
+ --background: hsl(222.2 84% 4.9%);
131
+ --foreground: hsl(210 40% 98%);
133
132
 
134
- --card: hsl(222.2 84% 4.9%);
135
- --card-foreground: hsl(210 40% 98%);
133
+ --card: hsl(222.2 84% 4.9%);
134
+ --card-foreground: hsl(210 40% 98%);
136
135
 
137
- --popover: hsl(222.2 84% 4.9%);
138
- --popover-foreground: hsl(210 40% 98%);
136
+ --popover: hsl(222.2 84% 4.9%);
137
+ --popover-foreground: hsl(210 40% 98%);
139
138
 
140
- --primary: hsl(210 40% 98%);
141
- --primary-foreground: hsl(222.2 47.4% 11.2%);
139
+ --primary: hsl(210 40% 98%);
140
+ --primary-foreground: hsl(222.2 47.4% 11.2%);
142
141
 
143
- --secondary: hsl(217.2 32.6% 17.5%);
144
- --secondary-foreground: hsl(210 40% 98%);
142
+ --secondary: hsl(217.2 32.6% 17.5%);
143
+ --secondary-foreground: hsl(210 40% 98%);
145
144
 
146
- --muted: hsl(217.2 32.6% 17.5%);
147
- --muted-foreground: hsl(215 20.2% 65.1%);
145
+ --muted: hsl(217.2 32.6% 17.5%);
146
+ --muted-foreground: hsl(215 20.2% 65.1%);
148
147
 
149
- --accent: hsl(217.2 32.6% 17.5%);
150
- --accent-foreground: hsl(210 40% 98%);
148
+ --accent: hsl(217.2 32.6% 17.5%);
149
+ --accent-foreground: hsl(210 40% 98%);
151
150
 
152
- --destructive: hsl(0 62.8% 30.6%);
153
- --destructive-foreground: hsl(210 40% 98%);
154
-
155
- --success: hsl(142.1 70.6% 45.3%);
156
- --success-foreground: hsl(222.2 47.4% 11.2%);
157
-
158
- --warning: hsl(48 96.5% 53.1%);
159
- --warning-foreground: hsl(222.2 47.4% 11.2%);
151
+ --destructive: hsl(0 62.8% 30.6%);
152
+ --destructive-foreground: hsl(210 40% 98%);
160
153
 
161
- --info: hsl(199.4 95.5% 53.8%);
162
- --info-foreground: hsl(222.2 47.4% 11.2%);
154
+ --success: hsl(142.1 70.6% 45.3%);
155
+ --success-foreground: hsl(222.2 47.4% 11.2%);
163
156
 
164
- --border: hsl(217.2 32.6% 17.5%);
165
- --input: hsl(217.2 32.6% 17.5%);
166
- --ring: hsl(212.7 26.8% 83.9%);
157
+ --warning: hsl(48 96.5% 53.1%);
158
+ --warning-foreground: hsl(222.2 47.4% 11.2%);
167
159
 
168
- --sidebar-background: hsl(222.2 84% 10%);
169
- --sidebar-foreground: hsl(210 40% 98%);
170
- --sidebar-primary: hsl(210 40% 98%);
171
- --sidebar-primary-foreground: hsl(222.2 47.4% 11.2%);
172
- --sidebar-accent: hsl(217.2 32.6% 17.5%);
173
- --sidebar-accent-foreground: hsl(210 40% 98%);
174
- --sidebar-border: hsl(217.2 32.6% 17.5%);
175
- --sidebar-ring: hsl(212.7 26.8% 83.9%);
176
- }
160
+ --info: hsl(199.4 95.5% 53.8%);
161
+ --info-foreground: hsl(222.2 47.4% 11.2%);
162
+
163
+ --border: hsl(217.2 32.6% 17.5%);
164
+ --input: hsl(217.2 32.6% 17.5%);
165
+ --ring: hsl(212.7 26.8% 83.9%);
166
+
167
+ --sidebar-background: hsl(222.2 84% 10%);
168
+ --sidebar-foreground: hsl(210 40% 98%);
169
+ --sidebar-primary: hsl(210 40% 98%);
170
+ --sidebar-primary-foreground: hsl(222.2 47.4% 11.2%);
171
+ --sidebar-accent: hsl(217.2 32.6% 17.5%);
172
+ --sidebar-accent-foreground: hsl(210 40% 98%);
173
+ --sidebar-border: hsl(217.2 32.6% 17.5%);
174
+ --sidebar-ring: hsl(212.7 26.8% 83.9%);
177
175
  }
178
176
 
179
177
  body {
@@ -126,54 +126,52 @@
126
126
  }
127
127
  }
128
128
 
129
- @media (prefers-color-scheme: dark) {
130
- :root {
131
- --background: hsl(24 9.8% 10%);
132
- --foreground: hsl(60 9.1% 97.8%);
129
+ .dark {
130
+ --background: hsl(24 9.8% 10%);
131
+ --foreground: hsl(60 9.1% 97.8%);
133
132
 
134
- --card: hsl(24 9.8% 10%);
135
- --card-foreground: hsl(60 9.1% 97.8%);
133
+ --card: hsl(24 9.8% 10%);
134
+ --card-foreground: hsl(60 9.1% 97.8%);
136
135
 
137
- --popover: hsl(24 9.8% 10%);
138
- --popover-foreground: hsl(60 9.1% 97.8%);
136
+ --popover: hsl(24 9.8% 10%);
137
+ --popover-foreground: hsl(60 9.1% 97.8%);
139
138
 
140
- --primary: hsl(60 9.1% 97.8%);
141
- --primary-foreground: hsl(24 9.8% 10%);
139
+ --primary: hsl(60 9.1% 97.8%);
140
+ --primary-foreground: hsl(24 9.8% 10%);
142
141
 
143
- --secondary: hsl(12 6.5% 15.1%);
144
- --secondary-foreground: hsl(60 9.1% 97.8%);
142
+ --secondary: hsl(12 6.5% 15.1%);
143
+ --secondary-foreground: hsl(60 9.1% 97.8%);
145
144
 
146
- --muted: hsl(12 6.5% 15.1%);
147
- --muted-foreground: hsl(24 5.4% 63.9%);
145
+ --muted: hsl(12 6.5% 15.1%);
146
+ --muted-foreground: hsl(24 5.4% 63.9%);
148
147
 
149
- --accent: hsl(12 6.5% 15.1%);
150
- --accent-foreground: hsl(60 9.1% 97.8%);
148
+ --accent: hsl(12 6.5% 15.1%);
149
+ --accent-foreground: hsl(60 9.1% 97.8%);
151
150
 
152
- --destructive: hsl(0 62.8% 30.6%);
153
- --destructive-foreground: hsl(60 9.1% 97.8%);
154
-
155
- --success: hsl(142.1 70.6% 45.3%);
156
- --success-foreground: hsl(24 9.8% 10%);
157
-
158
- --warning: hsl(48 96.5% 53.1%);
159
- --warning-foreground: hsl(24 9.8% 10%);
151
+ --destructive: hsl(0 62.8% 30.6%);
152
+ --destructive-foreground: hsl(60 9.1% 97.8%);
160
153
 
161
- --info: hsl(199.4 95.5% 53.8%);
162
- --info-foreground: hsl(24 9.8% 10%);
154
+ --success: hsl(142.1 70.6% 45.3%);
155
+ --success-foreground: hsl(24 9.8% 10%);
163
156
 
164
- --border: hsl(12 6.5% 15.1%);
165
- --input: hsl(12 6.5% 15.1%);
166
- --ring: hsl(24 5.7% 82.9%);
157
+ --warning: hsl(48 96.5% 53.1%);
158
+ --warning-foreground: hsl(24 9.8% 10%);
167
159
 
168
- --sidebar-background: hsl(24 9.8% 14%);
169
- --sidebar-foreground: hsl(60 9.1% 97.8%);
170
- --sidebar-primary: hsl(60 9.1% 97.8%);
171
- --sidebar-primary-foreground: hsl(24 9.8% 10%);
172
- --sidebar-accent: hsl(12 6.5% 15.1%);
173
- --sidebar-accent-foreground: hsl(60 9.1% 97.8%);
174
- --sidebar-border: hsl(12 6.5% 15.1%);
175
- --sidebar-ring: hsl(24 5.7% 82.9%);
176
- }
160
+ --info: hsl(199.4 95.5% 53.8%);
161
+ --info-foreground: hsl(24 9.8% 10%);
162
+
163
+ --border: hsl(12 6.5% 15.1%);
164
+ --input: hsl(12 6.5% 15.1%);
165
+ --ring: hsl(24 5.7% 82.9%);
166
+
167
+ --sidebar-background: hsl(24 9.8% 14%);
168
+ --sidebar-foreground: hsl(60 9.1% 97.8%);
169
+ --sidebar-primary: hsl(60 9.1% 97.8%);
170
+ --sidebar-primary-foreground: hsl(24 9.8% 10%);
171
+ --sidebar-accent: hsl(12 6.5% 15.1%);
172
+ --sidebar-accent-foreground: hsl(60 9.1% 97.8%);
173
+ --sidebar-border: hsl(12 6.5% 15.1%);
174
+ --sidebar-ring: hsl(24 5.7% 82.9%);
177
175
  }
178
176
 
179
177
  body {
@@ -126,54 +126,52 @@
126
126
  }
127
127
  }
128
128
 
129
- @media (prefers-color-scheme: dark) {
130
- :root {
131
- --background: hsl(246 20% 6%);
132
- --foreground: hsl(0 0% 98%);
129
+ .dark {
130
+ --background: hsl(246 20% 6%);
131
+ --foreground: hsl(0 0% 98%);
133
132
 
134
- --card: hsl(246 20% 8%);
135
- --card-foreground: hsl(0 0% 98%);
133
+ --card: hsl(246 20% 8%);
134
+ --card-foreground: hsl(0 0% 98%);
136
135
 
137
- --popover: hsl(246 20% 8%);
138
- --popover-foreground: hsl(0 0% 98%);
136
+ --popover: hsl(246 20% 8%);
137
+ --popover-foreground: hsl(0 0% 98%);
139
138
 
140
- --primary: hsl(246 80% 65%);
141
- --primary-foreground: hsl(246 20% 6%);
139
+ --primary: hsl(246 80% 65%);
140
+ --primary-foreground: hsl(246 20% 6%);
142
141
 
143
- --secondary: hsl(320 85% 60%);
144
- --secondary-foreground: hsl(0 0% 100%);
145
-
146
- --muted: hsl(246 20% 15%);
147
- --muted-foreground: hsl(246 10% 60%);
148
-
149
- --accent: hsl(320 85% 60%);
150
- --accent-foreground: hsl(0 0% 100%);
142
+ --secondary: hsl(320 85% 60%);
143
+ --secondary-foreground: hsl(0 0% 100%);
151
144
 
152
- --destructive: hsl(0 62.8% 30.6%);
153
- --destructive-foreground: hsl(0 0% 98%);
145
+ --muted: hsl(246 20% 15%);
146
+ --muted-foreground: hsl(246 10% 60%);
154
147
 
155
- --success: hsl(142.1 70.6% 45.3%);
156
- --success-foreground: hsl(246 80% 60%);
148
+ --accent: hsl(320 85% 60%);
149
+ --accent-foreground: hsl(0 0% 100%);
157
150
 
158
- --warning: hsl(48 96.5% 53.1%);
159
- --warning-foreground: hsl(246 80% 60%);
151
+ --destructive: hsl(0 62.8% 30.6%);
152
+ --destructive-foreground: hsl(0 0% 98%);
160
153
 
161
- --info: hsl(199.4 95.5% 53.8%);
162
- --info-foreground: hsl(246 80% 60%);
154
+ --success: hsl(142.1 70.6% 45.3%);
155
+ --success-foreground: hsl(246 80% 60%);
163
156
 
164
- --border: hsl(246 20% 18%);
165
- --input: hsl(246 20% 18%);
166
- --ring: hsl(246 80% 65%);
157
+ --warning: hsl(48 96.5% 53.1%);
158
+ --warning-foreground: hsl(246 80% 60%);
167
159
 
168
- --sidebar-background: hsl(246 20% 10%);
169
- --sidebar-foreground: hsl(0 0% 98%);
170
- --sidebar-primary: hsl(246 80% 65%);
171
- --sidebar-primary-foreground: hsl(246 20% 6%);
172
- --sidebar-accent: hsl(320 85% 60%);
173
- --sidebar-accent-foreground: hsl(0 0% 100%);
174
- --sidebar-border: hsl(246 20% 18%);
175
- --sidebar-ring: hsl(246 80% 65%);
176
- }
160
+ --info: hsl(199.4 95.5% 53.8%);
161
+ --info-foreground: hsl(246 80% 60%);
162
+
163
+ --border: hsl(246 20% 18%);
164
+ --input: hsl(246 20% 18%);
165
+ --ring: hsl(246 80% 65%);
166
+
167
+ --sidebar-background: hsl(246 20% 10%);
168
+ --sidebar-foreground: hsl(0 0% 98%);
169
+ --sidebar-primary: hsl(246 80% 65%);
170
+ --sidebar-primary-foreground: hsl(246 20% 6%);
171
+ --sidebar-accent: hsl(320 85% 60%);
172
+ --sidebar-accent-foreground: hsl(0 0% 100%);
173
+ --sidebar-border: hsl(246 20% 18%);
174
+ --sidebar-ring: hsl(246 80% 65%);
177
175
  }
178
176
 
179
177
  body {
@@ -126,54 +126,52 @@
126
126
  }
127
127
  }
128
128
 
129
- @media (prefers-color-scheme: dark) {
130
- :root {
131
- --background: hsl(240 10% 3.9%);
132
- --foreground: hsl(0 0% 98%);
129
+ .dark {
130
+ --background: hsl(240 10% 3.9%);
131
+ --foreground: hsl(0 0% 98%);
133
132
 
134
- --card: hsl(240 10% 3.9%);
135
- --card-foreground: hsl(0 0% 98%);
133
+ --card: hsl(240 10% 3.9%);
134
+ --card-foreground: hsl(0 0% 98%);
136
135
 
137
- --popover: hsl(240 10% 3.9%);
138
- --popover-foreground: hsl(0 0% 98%);
136
+ --popover: hsl(240 10% 3.9%);
137
+ --popover-foreground: hsl(0 0% 98%);
139
138
 
140
- --primary: hsl(0 0% 98%);
141
- --primary-foreground: hsl(240 5.9% 10%);
139
+ --primary: hsl(0 0% 98%);
140
+ --primary-foreground: hsl(240 5.9% 10%);
142
141
 
143
- --secondary: hsl(240 3.7% 15.9%);
144
- --secondary-foreground: hsl(0 0% 98%);
142
+ --secondary: hsl(240 3.7% 15.9%);
143
+ --secondary-foreground: hsl(0 0% 98%);
145
144
 
146
- --muted: hsl(240 3.7% 15.9%);
147
- --muted-foreground: hsl(240 5% 64.9%);
145
+ --muted: hsl(240 3.7% 15.9%);
146
+ --muted-foreground: hsl(240 5% 64.9%);
148
147
 
149
- --accent: hsl(240 3.7% 15.9%);
150
- --accent-foreground: hsl(0 0% 98%);
148
+ --accent: hsl(240 3.7% 15.9%);
149
+ --accent-foreground: hsl(0 0% 98%);
151
150
 
152
- --destructive: hsl(0 62.8% 30.6%);
153
- --destructive-foreground: hsl(0 0% 98%);
151
+ --destructive: hsl(0 62.8% 30.6%);
152
+ --destructive-foreground: hsl(0 0% 98%);
154
153
 
155
- --success: hsl(142.1 70.6% 45.3%);
156
- --success-foreground: hsl(240 5.9% 10%);
154
+ --success: hsl(142.1 70.6% 45.3%);
155
+ --success-foreground: hsl(240 5.9% 10%);
157
156
 
158
- --warning: hsl(48 96.5% 53.1%);
159
- --warning-foreground: hsl(240 5.9% 10%);
157
+ --warning: hsl(48 96.5% 53.1%);
158
+ --warning-foreground: hsl(240 5.9% 10%);
160
159
 
161
- --info: hsl(199.4 95.5% 53.8%);
162
- --info-foreground: hsl(240 5.9% 10%);
160
+ --info: hsl(199.4 95.5% 53.8%);
161
+ --info-foreground: hsl(240 5.9% 10%);
163
162
 
164
- --border: hsl(240 3.7% 15.9%);
165
- --input: hsl(240 3.7% 15.9%);
166
- --ring: hsl(240 4.9% 83.9%);
163
+ --border: hsl(240 3.7% 15.9%);
164
+ --input: hsl(240 3.7% 15.9%);
165
+ --ring: hsl(240 4.9% 83.9%);
167
166
 
168
- --sidebar-background: hsl(240 5.9% 10%);
169
- --sidebar-foreground: hsl(240 4.8% 95.9%);
170
- --sidebar-primary: hsl(224.3 76.3% 48%);
171
- --sidebar-primary-foreground: hsl(0 0% 100%);
172
- --sidebar-accent: hsl(240 3.7% 15.9%);
173
- --sidebar-accent-foreground: hsl(240 4.8% 95.9%);
174
- --sidebar-border: hsl(240 3.7% 15.9%);
175
- --sidebar-ring: hsl(217.2 91.2% 59.8%);
176
- }
167
+ --sidebar-background: hsl(240 5.9% 10%);
168
+ --sidebar-foreground: hsl(240 4.8% 95.9%);
169
+ --sidebar-primary: hsl(224.3 76.3% 48%);
170
+ --sidebar-primary-foreground: hsl(0 0% 100%);
171
+ --sidebar-accent: hsl(240 3.7% 15.9%);
172
+ --sidebar-accent-foreground: hsl(240 4.8% 95.9%);
173
+ --sidebar-border: hsl(240 3.7% 15.9%);
174
+ --sidebar-ring: hsl(217.2 91.2% 59.8%);
177
175
  }
178
176
 
179
177
  body {
@@ -1,7 +1,11 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from "react"
4
- import { ResponsiveContainer } from "recharts"
4
+ import {
5
+ Legend as ChartLegendPrimitive,
6
+ ResponsiveContainer,
7
+ Tooltip as ChartTooltipPrimitive,
8
+ } from "recharts"
5
9
 
6
10
  import { cn } from "@/lib/utils"
7
11
 
@@ -102,13 +106,19 @@ const ChartTooltip = ChartTooltipPrimitive
102
106
 
103
107
  const ChartTooltipContent = React.forwardRef<
104
108
  HTMLDivElement,
105
- React.ComponentProps<typeof ChartTooltipPrimitive> &
109
+ Omit<React.ComponentProps<typeof ChartTooltipPrimitive>, "payload"> &
106
110
  React.ComponentProps<"div"> & {
107
111
  hideLabel?: boolean
108
112
  hideIndicator?: boolean
109
113
  indicator?: "line" | "dot" | "dashed"
110
114
  nameKey?: string
111
115
  labelKey?: string
116
+ payload?: any[]
117
+ label?: any
118
+ labelFormatter?: any
119
+ labelClassName?: string
120
+ formatter?: any
121
+ color?: string
112
122
  }
113
123
  >(
114
124
  (
@@ -181,7 +191,7 @@ const ChartTooltipContent = React.forwardRef<
181
191
  >
182
192
  {!nestLabel ? tooltipLabel : null}
183
193
  <div className="grid gap-1.5">
184
- {payload.map((item, index) => {
194
+ {payload.map((item: any, index: number) => {
185
195
  const key = `${nameKey || item.name || item.dataKey || "value"}`
186
196
  const itemConfig = getPayloadConfigFromPayload(config, item, key)
187
197
  const indicatorColor = color || item.payload.fill || item.color
@@ -257,7 +267,9 @@ const ChartLegend = ChartLegendPrimitive
257
267
  const ChartLegendContent = React.forwardRef<
258
268
  HTMLDivElement,
259
269
  React.ComponentProps<"div"> &
260
- Pick<React.ComponentProps<typeof ChartLegendPrimitive>, "payload" | "verticalAlign"> & {
270
+ {
271
+ payload?: any[]
272
+ verticalAlign?: "top" | "middle" | "bottom"
261
273
  hideIcon?: boolean
262
274
  nameKey?: string
263
275
  }
@@ -281,7 +293,7 @@ const ChartLegendContent = React.forwardRef<
281
293
  className
282
294
  )}
283
295
  >
284
- {payload.map((item) => {
296
+ {(payload as any[]).map((item) => {
285
297
  const key = `${nameKey || item.dataKey || "value"}`
286
298
  const itemConfig = getPayloadConfigFromPayload(config, item, key)
287
299
 
@@ -351,16 +363,6 @@ function getPayloadConfigFromPayload(
351
363
  : config[key as keyof typeof config]
352
364
  }
353
365
 
354
- // Recharts Primitive Imports - We import specific components to avoid large bundle reference but for now let's just use * and assume tree shaking or specific imports in consuming app.
355
- // However, the above code uses RechartsPrimitive.Tooltip and Legend.
356
- // Ideally we should import them from recharts.
357
-
358
- import {
359
- Legend as ChartLegendPrimitive,
360
- Tooltip as ChartTooltipPrimitive,
361
- ResponsiveContainer,
362
- } from "recharts"
363
-
364
366
  export {
365
367
  ChartContainer,
366
368
  ChartTooltip,
@@ -14,6 +14,7 @@ interface DropdownMenuProps {
14
14
  open?: boolean
15
15
  onOpenChange?: (open: boolean) => void
16
16
  defaultOpen?: boolean
17
+ className?: string
17
18
  }
18
19
 
19
20
  /**
@@ -32,7 +33,7 @@ interface DropdownMenuProps {
32
33
  * </DropdownMenuContent>
33
34
  * </DropdownMenu>
34
35
  */
35
- function DropdownMenu({ children, open: controlledOpen, onOpenChange, defaultOpen = false }: DropdownMenuProps) {
36
+ function DropdownMenu({ children, open: controlledOpen, onOpenChange, defaultOpen = false, className }: DropdownMenuProps) {
36
37
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
37
38
  const triggerRef = React.useRef<HTMLButtonElement>(null)
38
39
 
@@ -41,13 +42,14 @@ function DropdownMenu({ children, open: controlledOpen, onOpenChange, defaultOpe
41
42
 
42
43
  return (
43
44
  <DropdownMenuContext.Provider value={{ open, onOpenChange: setOpen, triggerRef }}>
44
- <div className="relative inline-block text-left">
45
+ <div className={cn("relative inline-block text-left", className)}>
45
46
  {children}
46
47
  </div>
47
48
  </DropdownMenuContext.Provider>
48
49
  )
49
50
  }
50
51
 
52
+
51
53
  interface DropdownMenuTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
52
54
  asChild?: boolean
53
55
  }
@@ -107,6 +109,11 @@ const DropdownMenuContent = React.forwardRef<HTMLDivElement, DropdownMenuContent
107
109
  const context = React.useContext(DropdownMenuContext)
108
110
  if (!context) throw new Error("DropdownMenuContent must be used within DropdownMenu")
109
111
  const contentRef = React.useRef<HTMLDivElement>(null)
112
+ const [currentSide, setCurrentSide] = React.useState(side)
113
+
114
+ React.useEffect(() => {
115
+ setCurrentSide(side)
116
+ }, [side])
110
117
 
111
118
  React.useEffect(() => {
112
119
  const handleClickOutside = (e: MouseEvent) => {
@@ -129,17 +136,53 @@ const DropdownMenuContent = React.forwardRef<HTMLDivElement, DropdownMenuContent
129
136
  }
130
137
  }
131
138
 
139
+ const checkPosition = () => {
140
+ if (context.open && contentRef.current && context.triggerRef.current) {
141
+ const triggerRect = context.triggerRef.current.getBoundingClientRect()
142
+ const contentRect = contentRef.current.getBoundingClientRect()
143
+ const viewportHeight = window.innerHeight
144
+
145
+ const spaceBelow = viewportHeight - triggerRect.bottom
146
+ const spaceAbove = triggerRect.top
147
+ const neededHeight = contentRect.height + sideOffset
148
+
149
+ if (side === 'bottom') {
150
+ if (spaceBelow < neededHeight && spaceAbove > neededHeight) {
151
+ setCurrentSide('top')
152
+ } else {
153
+ setCurrentSide('bottom')
154
+ }
155
+ } else if (side === 'top') {
156
+ if (spaceAbove < neededHeight && spaceBelow > neededHeight) {
157
+ setCurrentSide('bottom')
158
+ } else {
159
+ setCurrentSide('top')
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ if (context.open) {
166
+ // Check position immediately after render cycle (via timeout to allow ref to populate and layout to occur)
167
+ // Using requestAnimationFrame for better timing in layout cycle
168
+ requestAnimationFrame(checkPosition)
169
+ }
170
+
132
171
  const timer = setTimeout(() => {
133
172
  document.addEventListener("click", handleClickOutside)
134
173
  }, 0)
135
174
  document.addEventListener("keydown", handleEscape)
175
+ window.addEventListener("resize", checkPosition)
176
+ window.addEventListener("scroll", checkPosition, true) // Capture scroll to update pos
136
177
 
137
178
  return () => {
138
179
  clearTimeout(timer)
139
180
  document.removeEventListener("click", handleClickOutside)
140
181
  document.removeEventListener("keydown", handleEscape)
182
+ window.removeEventListener("resize", checkPosition)
183
+ window.removeEventListener("scroll", checkPosition, true)
141
184
  }
142
- }, [context.open, context])
185
+ }, [context.open, context, side, sideOffset])
143
186
 
144
187
  if (!context.open) return null
145
188
 
@@ -150,12 +193,6 @@ const DropdownMenuContent = React.forwardRef<HTMLDivElement, DropdownMenuContent
150
193
  end: 'right-0',
151
194
  }
152
195
 
153
- // Calculate side classes
154
- const sideClasses = {
155
- bottom: `top-full mt-${sideOffset}`,
156
- top: `bottom-full mb-${sideOffset}`,
157
- }
158
-
159
196
  return (
160
197
  <div
161
198
  ref={(node) => {
@@ -169,12 +206,12 @@ const DropdownMenuContent = React.forwardRef<HTMLDivElement, DropdownMenuContent
169
206
  "absolute z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
170
207
  "animate-in fade-in-0 zoom-in-95",
171
208
  alignmentClasses[align],
172
- side === 'bottom' ? 'top-full' : 'bottom-full',
209
+ currentSide === 'bottom' ? 'top-full' : 'bottom-full',
173
210
  className
174
211
  )}
175
212
  style={{
176
- marginTop: side === 'bottom' ? sideOffset : undefined,
177
- marginBottom: side === 'top' ? sideOffset : undefined,
213
+ marginTop: currentSide === 'bottom' ? sideOffset : undefined,
214
+ marginBottom: currentSide === 'top' ? sideOffset : undefined,
178
215
  }}
179
216
  {...props}
180
217
  />
@@ -183,16 +220,54 @@ const DropdownMenuContent = React.forwardRef<HTMLDivElement, DropdownMenuContent
183
220
  )
184
221
  DropdownMenuContent.displayName = "DropdownMenuContent"
185
222
 
223
+ const DropdownMenuGroup = React.forwardRef<
224
+ HTMLDivElement,
225
+ React.HTMLAttributes<HTMLDivElement>
226
+ >(({ className, ...props }, ref) => (
227
+ <div ref={ref} className={cn("", className)} {...props} />
228
+ ))
229
+ DropdownMenuGroup.displayName = "DropdownMenuGroup"
230
+
231
+ const DropdownMenuPortal = ({ children }: { children: React.ReactNode }) => {
232
+ return <>{children}</>
233
+ }
234
+ DropdownMenuPortal.displayName = "DropdownMenuPortal"
235
+
186
236
  const DropdownMenuItem = React.forwardRef<
187
237
  HTMLDivElement,
188
- React.HTMLAttributes<HTMLDivElement> & { inset?: boolean; disabled?: boolean }
189
- >(({ className, inset, disabled, onClick, ...props }, ref) => {
238
+ React.HTMLAttributes<HTMLDivElement> & { inset?: boolean; disabled?: boolean; asChild?: boolean; closeOnSelect?: boolean }
239
+ >(({ className, inset, disabled, onClick, asChild = false, closeOnSelect = true, ...props }, ref) => {
190
240
  const context = React.useContext(DropdownMenuContext)
191
241
 
192
242
  const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
193
243
  if (disabled) return
194
244
  onClick?.(e)
195
- context?.onOpenChange(false)
245
+ if (closeOnSelect) {
246
+ context?.onOpenChange(false)
247
+ }
248
+ }
249
+
250
+ if (asChild) {
251
+ const child = React.Children.only(props.children) as React.ReactElement<any>
252
+ return React.cloneElement(child, {
253
+ ref,
254
+ className: cn(
255
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground",
256
+ inset && "pl-8",
257
+ disabled && "pointer-events-none opacity-50",
258
+ className,
259
+ child.props.className
260
+ ),
261
+ onClick: (e: React.MouseEvent<HTMLDivElement>) => {
262
+ handleClick(e)
263
+ child.props.onClick?.(e)
264
+ },
265
+ "data-disabled": disabled || undefined,
266
+ "data-inset": inset || undefined,
267
+ tabIndex: disabled ? -1 : 0,
268
+ ...props,
269
+ children: child.props.children
270
+ })
196
271
  }
197
272
 
198
273
  return (
@@ -403,4 +478,6 @@ export {
403
478
  DropdownMenuSub,
404
479
  DropdownMenuSubTrigger,
405
480
  DropdownMenuSubContent,
481
+ DropdownMenuGroup,
482
+ DropdownMenuPortal,
406
483
  }