@olympusoss/canvas 2.6.28 → 2.7.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olympusoss/canvas",
3
- "version": "2.6.28",
3
+ "version": "2.7.1",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -15,7 +15,7 @@ const buttonVariants = cva(
15
15
  "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
16
16
  secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
17
17
  ghost: "hover:bg-accent hover:text-accent-foreground",
18
- link: "text-primary underline-offset-4 hover:underline",
18
+ link: "text-primary hover:text-brand",
19
19
  },
20
20
  size: {
21
21
  default: "h-9 px-4 py-2",
@@ -37,7 +37,8 @@ export interface ButtonProps
37
37
  /**
38
38
  * Visual emphasis preset. `default` is the primary action, `destructive`
39
39
  * is for irreversible actions, `outline` and `secondary` are quieter,
40
- * `ghost` is borderless, `link` looks like body-text underlined.
40
+ * `ghost` is borderless, `link` looks like body-text with a soft
41
+ * color shift on hover (no underline).
41
42
  * @default "default"
42
43
  */
43
44
  variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
@@ -41,17 +41,29 @@ export const ServiceHealthList = React.forwardRef<HTMLDivElement, ServiceHealthL
41
41
  <ul className="flex flex-col gap-2.5">
42
42
  {items.map((item) => {
43
43
  const hsl = DOT_TOKENS[item.status];
44
+ const isHealthy = item.status === "healthy";
44
45
  return (
45
46
  <li key={item.name} className="flex items-center gap-2.5 text-[13px]">
46
- <span
47
- role="img"
48
- className="size-2 shrink-0 rounded-full"
49
- style={{
50
- background: `hsl(${hsl})`,
51
- boxShadow: `0 0 0 3px hsl(${hsl} / 0.18)`,
52
- }}
53
- aria-label={`Status: ${item.status}`}
54
- />
47
+ <span className="relative flex size-2 shrink-0">
48
+ {isHealthy && (
49
+ <span
50
+ aria-hidden
51
+ className="absolute inline-flex h-full w-full animate-ping rounded-full opacity-75"
52
+ style={{ background: `hsl(${hsl})` }}
53
+ />
54
+ )}
55
+ <span
56
+ role="img"
57
+ aria-label={`Status: ${item.status}`}
58
+ className="relative inline-flex size-2 rounded-full"
59
+ style={{
60
+ background: `hsl(${hsl})`,
61
+ boxShadow: isHealthy
62
+ ? `0 0 6px hsl(${hsl}), 0 0 0 3px hsl(${hsl} / 0.18)`
63
+ : `0 0 0 3px hsl(${hsl} / 0.18)`,
64
+ }}
65
+ />
66
+ </span>
55
67
  <span className="flex-1 font-medium">{item.name}</span>
56
68
  {item.meta?.map((cell, i) => (
57
69
  <span
@@ -76,11 +76,7 @@ const BreadcrumbLink = React.forwardRef<HTMLAnchorElement, BreadcrumbLinkProps>(
76
76
  const Comp = asChild ? Slot : "a";
77
77
 
78
78
  return (
79
- <Comp
80
- ref={ref}
81
- className={cn("transition-colors hover:text-foreground", className)}
82
- {...props}
83
- />
79
+ <Comp ref={ref} className={cn("transition-colors hover:text-brand", className)} {...props} />
84
80
  );
85
81
  },
86
82
  );
@@ -44,11 +44,11 @@ export function PageHeader({
44
44
  <span key={crumb.label} className="flex items-center gap-1">
45
45
  {crumb.href ? (
46
46
  LinkComp ? (
47
- <LinkComp href={crumb.href} className="hover:text-foreground transition-colors">
47
+ <LinkComp href={crumb.href} className="hover:text-brand transition-colors">
48
48
  {crumb.label}
49
49
  </LinkComp>
50
50
  ) : (
51
- <a href={crumb.href} className="hover:text-foreground transition-colors">
51
+ <a href={crumb.href} className="hover:text-brand transition-colors">
52
52
  {crumb.label}
53
53
  </a>
54
54
  )
@@ -3,6 +3,7 @@
3
3
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
4
4
  import * as React from "react";
5
5
 
6
+ import { usePortalContainer } from "../../lib/portal-container";
6
7
  import { cn } from "../../lib/utils";
7
8
 
8
9
  export interface TooltipProviderProps
@@ -129,22 +130,25 @@ export interface TooltipContentProps
129
130
  const TooltipContent = React.forwardRef<
130
131
  React.ElementRef<typeof TooltipPrimitive.Content>,
131
132
  TooltipContentProps
132
- >(({ className, sideOffset = 6, children, ...props }, ref) => (
133
- <TooltipPrimitive.Portal>
134
- <TooltipPrimitive.Content
135
- ref={ref}
136
- sideOffset={sideOffset}
137
- className={cn(
138
- "z-50 max-w-xs overflow-hidden rounded-md border border-border/50 bg-popover px-2 py-1 text-[11px] font-medium text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 origin-[var(--radix-tooltip-content-transform-origin)]",
139
- className,
140
- )}
141
- {...props}
142
- >
143
- {children}
144
- <TooltipPrimitive.Arrow width={10} height={5} style={{ fill: "hsl(var(--popover))" }} />
145
- </TooltipPrimitive.Content>
146
- </TooltipPrimitive.Portal>
147
- ));
133
+ >(({ className, sideOffset = 6, children, ...props }, ref) => {
134
+ const container = usePortalContainer();
135
+ return (
136
+ <TooltipPrimitive.Portal container={container ?? undefined}>
137
+ <TooltipPrimitive.Content
138
+ ref={ref}
139
+ sideOffset={sideOffset}
140
+ className={cn(
141
+ "z-50 max-w-xs overflow-hidden rounded-md border border-border/50 bg-popover px-2 py-1 text-[11px] font-medium text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 origin-[var(--radix-tooltip-content-transform-origin)]",
142
+ className,
143
+ )}
144
+ {...props}
145
+ >
146
+ {children}
147
+ <TooltipPrimitive.Arrow width={10} height={5} style={{ fill: "hsl(var(--popover))" }} />
148
+ </TooltipPrimitive.Content>
149
+ </TooltipPrimitive.Portal>
150
+ );
151
+ });
148
152
  TooltipContent.displayName = TooltipPrimitive.Content.displayName;
149
153
 
150
154
  export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
@@ -103,7 +103,7 @@ const AccordionTrigger = React.forwardRef<
103
103
  <AccordionPrimitive.Trigger
104
104
  ref={ref}
105
105
  className={cn(
106
- "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
106
+ "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-colors hover:text-brand text-left [&[data-state=open]>svg]:rotate-180",
107
107
  className,
108
108
  )}
109
109
  {...props}
@@ -3,6 +3,7 @@
3
3
  import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
4
4
  import * as React from "react";
5
5
 
6
+ import { usePortalContainer } from "../../lib/portal-container";
6
7
  import { cn } from "../../lib/utils";
7
8
  import { buttonVariants } from "../atoms/button";
8
9
 
@@ -115,19 +116,22 @@ export interface AlertDialogContentProps
115
116
  const AlertDialogContent = React.forwardRef<
116
117
  React.ElementRef<typeof AlertDialogPrimitive.Content>,
117
118
  AlertDialogContentProps
118
- >(({ className, ...props }, ref) => (
119
- <AlertDialogPortal>
120
- <AlertDialogOverlay />
121
- <AlertDialogPrimitive.Content
122
- ref={ref}
123
- className={cn(
124
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border border-border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
125
- className,
126
- )}
127
- {...props}
128
- />
129
- </AlertDialogPortal>
130
- ));
119
+ >(({ className, ...props }, ref) => {
120
+ const container = usePortalContainer();
121
+ return (
122
+ <AlertDialogPortal container={container ?? undefined}>
123
+ <AlertDialogOverlay />
124
+ <AlertDialogPrimitive.Content
125
+ ref={ref}
126
+ className={cn(
127
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border border-border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
128
+ className,
129
+ )}
130
+ {...props}
131
+ />
132
+ </AlertDialogPortal>
133
+ );
134
+ });
131
135
  AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
132
136
 
133
137
  export interface AlertDialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -191,7 +191,7 @@ const CarouselPrevious = React.forwardRef<HTMLButtonElement, React.ComponentProp
191
191
  "absolute h-8 w-8 rounded-full",
192
192
  orientation === "horizontal"
193
193
  ? "-left-12 top-1/2 -translate-y-1/2"
194
- : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
194
+ : "-top-10 left-1/2 -translate-x-1/2 rotate-90",
195
195
  className,
196
196
  )}
197
197
  disabled={!canScrollPrev}
@@ -219,7 +219,7 @@ const CarouselNext = React.forwardRef<HTMLButtonElement, React.ComponentProps<ty
219
219
  "absolute h-8 w-8 rounded-full",
220
220
  orientation === "horizontal"
221
221
  ? "-right-12 top-1/2 -translate-y-1/2"
222
- : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
222
+ : "-bottom-10 left-1/2 -translate-x-1/2 rotate-90",
223
223
  className,
224
224
  )}
225
225
  disabled={!canScrollNext}
@@ -4,6 +4,7 @@ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
4
4
  import { Check, ChevronRight, Circle } from "lucide-react";
5
5
  import * as React from "react";
6
6
 
7
+ import { usePortalContainer } from "../../lib/portal-container";
7
8
  import { cn } from "../../lib/utils";
8
9
 
9
10
  export interface ContextMenuProps extends React.ComponentProps<typeof ContextMenuPrimitive.Root> {
@@ -154,18 +155,21 @@ export interface ContextMenuContentProps
154
155
  const ContextMenuContent = React.forwardRef<
155
156
  React.ElementRef<typeof ContextMenuPrimitive.Content>,
156
157
  ContextMenuContentProps
157
- >(({ className, ...props }, ref) => (
158
- <ContextMenuPrimitive.Portal>
159
- <ContextMenuPrimitive.Content
160
- ref={ref}
161
- className={cn(
162
- "z-50 max-h-[var(--radix-context-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md 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 origin-[var(--radix-context-menu-content-transform-origin)]",
163
- className,
164
- )}
165
- {...props}
166
- />
167
- </ContextMenuPrimitive.Portal>
168
- ));
158
+ >(({ className, ...props }, ref) => {
159
+ const container = usePortalContainer();
160
+ return (
161
+ <ContextMenuPrimitive.Portal container={container ?? undefined}>
162
+ <ContextMenuPrimitive.Content
163
+ ref={ref}
164
+ className={cn(
165
+ "z-50 max-h-[var(--radix-context-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md 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 origin-[var(--radix-context-menu-content-transform-origin)]",
166
+ className,
167
+ )}
168
+ {...props}
169
+ />
170
+ </ContextMenuPrimitive.Portal>
171
+ );
172
+ });
169
173
  ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
170
174
 
171
175
  export interface ContextMenuItemProps
@@ -4,6 +4,7 @@ import * as DialogPrimitive from "@radix-ui/react-dialog";
4
4
  import { X } from "lucide-react";
5
5
  import * as React from "react";
6
6
 
7
+ import { usePortalContainer } from "../../lib/portal-container";
7
8
  import { cn } from "../../lib/utils";
8
9
 
9
10
  /* ────────────────────────────────────────────────────────────────
@@ -183,25 +184,28 @@ export interface DialogContentProps
183
184
  const DialogContent = React.forwardRef<
184
185
  React.ElementRef<typeof DialogPrimitive.Content>,
185
186
  DialogContentProps
186
- >(({ className, children, ...props }, ref) => (
187
- <DialogPortal>
188
- <DialogOverlay />
189
- <DialogPrimitive.Content
190
- ref={ref}
191
- className={cn(
192
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border border-border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
193
- className,
194
- )}
195
- {...props}
196
- >
197
- {children}
198
- <DialogPrimitive.Close className="absolute right-4 top-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 data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
199
- <X className="h-4 w-4" />
200
- <span className="sr-only">Close</span>
201
- </DialogPrimitive.Close>
202
- </DialogPrimitive.Content>
203
- </DialogPortal>
204
- ));
187
+ >(({ className, children, ...props }, ref) => {
188
+ const container = usePortalContainer();
189
+ return (
190
+ <DialogPortal container={container ?? undefined}>
191
+ <DialogOverlay />
192
+ <DialogPrimitive.Content
193
+ ref={ref}
194
+ className={cn(
195
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border border-border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
196
+ className,
197
+ )}
198
+ {...props}
199
+ >
200
+ {children}
201
+ <DialogPrimitive.Close className="absolute right-4 top-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 data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
202
+ <X className="h-4 w-4" />
203
+ <span className="sr-only">Close</span>
204
+ </DialogPrimitive.Close>
205
+ </DialogPrimitive.Content>
206
+ </DialogPortal>
207
+ );
208
+ });
205
209
  DialogContent.displayName = DialogPrimitive.Content.displayName;
206
210
 
207
211
  export interface DialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -3,14 +3,23 @@
3
3
  import * as React from "react";
4
4
  import { Drawer as DrawerPrimitive } from "vaul";
5
5
 
6
+ import { usePortalContainer } from "../../lib/portal-container";
6
7
  import { cn } from "../../lib/utils";
7
8
 
8
9
  const Drawer = ({
9
10
  shouldScaleBackground = true,
11
+ container,
10
12
  ...props
11
- }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
12
- <DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />
13
- );
13
+ }: React.ComponentProps<typeof DrawerPrimitive.Root>) => {
14
+ const fallbackContainer = usePortalContainer();
15
+ return (
16
+ <DrawerPrimitive.Root
17
+ shouldScaleBackground={shouldScaleBackground}
18
+ container={container ?? fallbackContainer ?? undefined}
19
+ {...props}
20
+ />
21
+ );
22
+ };
14
23
  Drawer.displayName = "Drawer";
15
24
 
16
25
  const DrawerTrigger = DrawerPrimitive.Trigger;
@@ -31,6 +40,18 @@ const DrawerOverlay = React.forwardRef<
31
40
  ));
32
41
  DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
33
42
 
43
+ const DrawerHandle = React.forwardRef<
44
+ React.ElementRef<typeof DrawerPrimitive.Handle>,
45
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Handle>
46
+ >(({ className, ...props }, ref) => (
47
+ <DrawerPrimitive.Handle
48
+ ref={ref}
49
+ className={cn("mx-auto mt-4 !h-2 !w-[100px] rounded-full bg-muted", className)}
50
+ {...props}
51
+ />
52
+ ));
53
+ DrawerHandle.displayName = "DrawerHandle";
54
+
34
55
  const DrawerContent = React.forwardRef<
35
56
  React.ElementRef<typeof DrawerPrimitive.Content>,
36
57
  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
@@ -45,7 +66,7 @@ const DrawerContent = React.forwardRef<
45
66
  )}
46
67
  {...props}
47
68
  >
48
- <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
69
+ <DrawerHandle />
49
70
  {children}
50
71
  </DrawerPrimitive.Content>
51
72
  </DrawerPortal>
@@ -92,6 +113,7 @@ export {
92
113
  DrawerContent,
93
114
  DrawerDescription,
94
115
  DrawerFooter,
116
+ DrawerHandle,
95
117
  DrawerHeader,
96
118
  DrawerOverlay,
97
119
  DrawerPortal,
@@ -4,6 +4,7 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
4
4
  import { Check, ChevronRight, Circle } from "lucide-react";
5
5
  import * as React from "react";
6
6
 
7
+ import { usePortalContainer } from "../../lib/portal-container";
7
8
  import { cn } from "../../lib/utils";
8
9
 
9
10
  export interface DropdownMenuProps extends React.ComponentProps<typeof DropdownMenuPrimitive.Root> {
@@ -220,20 +221,23 @@ export interface DropdownMenuContentProps
220
221
  const DropdownMenuContent = React.forwardRef<
221
222
  React.ElementRef<typeof DropdownMenuPrimitive.Content>,
222
223
  DropdownMenuContentProps
223
- >(({ className, sideOffset = 4, ...props }, ref) => (
224
- <DropdownMenuPrimitive.Portal>
225
- <DropdownMenuPrimitive.Content
226
- ref={ref}
227
- sideOffset={sideOffset}
228
- className={cn(
229
- "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md",
230
- "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 origin-[var(--radix-dropdown-menu-content-transform-origin)]",
231
- className,
232
- )}
233
- {...props}
234
- />
235
- </DropdownMenuPrimitive.Portal>
236
- ));
224
+ >(({ className, sideOffset = 4, ...props }, ref) => {
225
+ const container = usePortalContainer();
226
+ return (
227
+ <DropdownMenuPrimitive.Portal container={container ?? undefined}>
228
+ <DropdownMenuPrimitive.Content
229
+ ref={ref}
230
+ sideOffset={sideOffset}
231
+ className={cn(
232
+ "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md",
233
+ "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 origin-[var(--radix-dropdown-menu-content-transform-origin)]",
234
+ className,
235
+ )}
236
+ {...props}
237
+ />
238
+ </DropdownMenuPrimitive.Portal>
239
+ );
240
+ });
237
241
  DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
238
242
 
239
243
  export interface DropdownMenuItemProps
@@ -23,8 +23,8 @@ export const PROSE_CANVAS_CLASSES = [
23
23
  "[&_em]:italic",
24
24
  "[&_u]:underline [&_u]:underline-offset-2",
25
25
  "[&_s]:line-through",
26
- // links
27
- "[&_a]:text-brand [&_a]:underline [&_a]:underline-offset-4 [&_a]:decoration-brand/40 hover:[&_a]:decoration-brand",
26
+ // links — brand color, fades slightly on hover (no underline)
27
+ "[&_a]:text-brand [&_a]:transition-colors hover:[&_a]:text-brand/80",
28
28
  // code
29
29
  "[&_code]:rounded [&_code]:bg-muted [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.85em] [&_code]:text-foreground",
30
30
  "[&_pre]:rounded-md [&_pre]:border [&_pre]:border-border [&_pre]:bg-muted/50 [&_pre]:p-3 [&_pre]:my-3 [&_pre]:overflow-x-auto",
@@ -4,6 +4,7 @@ import * as MenubarPrimitive from "@radix-ui/react-menubar";
4
4
  import { Check, ChevronRight, Circle } from "lucide-react";
5
5
  import * as React from "react";
6
6
 
7
+ import { usePortalContainer } from "../../lib/portal-container";
7
8
  import { cn } from "../../lib/utils";
8
9
 
9
10
  export interface MenubarProps extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root> {
@@ -272,21 +273,24 @@ export interface MenubarContentProps
272
273
  const MenubarContent = React.forwardRef<
273
274
  React.ElementRef<typeof MenubarPrimitive.Content>,
274
275
  MenubarContentProps
275
- >(({ className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, ref) => (
276
- <MenubarPrimitive.Portal>
277
- <MenubarPrimitive.Content
278
- ref={ref}
279
- align={align}
280
- alignOffset={alignOffset}
281
- sideOffset={sideOffset}
282
- className={cn(
283
- "z-50 min-w-[12rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in 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 origin-[var(--radix-menubar-content-transform-origin)]",
284
- className,
285
- )}
286
- {...props}
287
- />
288
- </MenubarPrimitive.Portal>
289
- ));
276
+ >(({ className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, ref) => {
277
+ const container = usePortalContainer();
278
+ return (
279
+ <MenubarPrimitive.Portal container={container ?? undefined}>
280
+ <MenubarPrimitive.Content
281
+ ref={ref}
282
+ align={align}
283
+ alignOffset={alignOffset}
284
+ sideOffset={sideOffset}
285
+ className={cn(
286
+ "z-50 min-w-[12rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in 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 origin-[var(--radix-menubar-content-transform-origin)]",
287
+ className,
288
+ )}
289
+ {...props}
290
+ />
291
+ </MenubarPrimitive.Portal>
292
+ );
293
+ });
290
294
  MenubarContent.displayName = MenubarPrimitive.Content.displayName;
291
295
 
292
296
  export interface MenubarItemProps
@@ -47,7 +47,7 @@ const NavBar = React.forwardRef<HTMLElement, NavBarProps>(
47
47
  <LinkEl
48
48
  key={link.href}
49
49
  href={link.href}
50
- className="text-sm font-medium text-muted-foreground no-underline transition-colors hover:text-foreground"
50
+ className="text-sm font-medium text-muted-foreground no-underline transition-colors hover:text-brand"
51
51
  {...(link.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
52
52
  >
53
53
  {link.label}
@@ -79,7 +79,7 @@ const NavBar = React.forwardRef<HTMLElement, NavBarProps>(
79
79
  key={link.href}
80
80
  href={link.href}
81
81
  onClick={() => setMobileOpen(false)}
82
- className="rounded-md px-3 py-2.5 text-sm font-medium text-muted-foreground no-underline transition-colors hover:bg-accent hover:text-foreground"
82
+ className="rounded-md px-3 py-2.5 text-sm font-medium text-muted-foreground no-underline transition-colors hover:bg-accent hover:text-brand"
83
83
  {...(link.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
84
84
  >
85
85
  {link.label}
@@ -3,6 +3,7 @@
3
3
  import * as PopoverPrimitive from "@radix-ui/react-popover";
4
4
  import * as React from "react";
5
5
 
6
+ import { usePortalContainer } from "../../lib/portal-container";
6
7
  import { cn } from "../../lib/utils";
7
8
 
8
9
  export interface PopoverProps extends React.ComponentProps<typeof PopoverPrimitive.Root> {
@@ -125,20 +126,23 @@ export interface PopoverContentProps
125
126
  const PopoverContent = React.forwardRef<
126
127
  React.ElementRef<typeof PopoverPrimitive.Content>,
127
128
  PopoverContentProps
128
- >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
129
- <PopoverPrimitive.Portal>
130
- <PopoverPrimitive.Content
131
- ref={ref}
132
- align={align}
133
- sideOffset={sideOffset}
134
- className={cn(
135
- "z-50 w-72 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none 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 origin-[var(--radix-popover-content-transform-origin)]",
136
- className,
137
- )}
138
- {...props}
139
- />
140
- </PopoverPrimitive.Portal>
141
- ));
129
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => {
130
+ const container = usePortalContainer();
131
+ return (
132
+ <PopoverPrimitive.Portal container={container ?? undefined}>
133
+ <PopoverPrimitive.Content
134
+ ref={ref}
135
+ align={align}
136
+ sideOffset={sideOffset}
137
+ className={cn(
138
+ "z-50 w-72 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none 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 origin-[var(--radix-popover-content-transform-origin)]",
139
+ className,
140
+ )}
141
+ {...props}
142
+ />
143
+ </PopoverPrimitive.Portal>
144
+ );
145
+ });
142
146
  PopoverContent.displayName = PopoverPrimitive.Content.displayName;
143
147
 
144
148
  export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };
@@ -5,11 +5,30 @@ import { Group, Panel, Separator } from "react-resizable-panels";
5
5
 
6
6
  import { cn } from "../../lib/utils";
7
7
 
8
- const ResizablePanelGroup = ({ className, ...props }: React.ComponentProps<typeof Group>) => (
9
- <Group
10
- className={cn("flex h-full w-full data-[panel-group-direction=vertical]:flex-col", className)}
11
- {...props}
12
- />
8
+ /**
9
+ * `react-resizable-panels` v4 emits `data-group` on the panel group and forces
10
+ * inline `height: 100%; width: 100%`, which means a `className="h-32"` passed
11
+ * directly to the group is ignored. The library expects the parent to size it.
12
+ *
13
+ * To keep `<ResizablePanelGroup className="h-32">` ergonomic for consumers, we
14
+ * wrap the library's `Group` in a sizing container — `className` lands on that
15
+ * wrapper, the inner `Group` then fills 100%.
16
+ *
17
+ * Orientation note: the library only emits `aria-orientation` on the
18
+ * `Separator` (and the separator's orientation is OPPOSITE the group's — a
19
+ * horizontal group has vertical separators). So group flex direction is set
20
+ * here in JS from the prop, while separator dimension styles key off
21
+ * `aria-orientation=horizontal` (separator inside a `vertical` group).
22
+ */
23
+ const ResizablePanelGroup = ({
24
+ className,
25
+ orientation,
26
+ style,
27
+ ...props
28
+ }: React.ComponentProps<typeof Group>) => (
29
+ <div className={cn("flex h-full w-full", className)} style={style}>
30
+ <Group orientation={orientation} {...props} />
31
+ </div>
13
32
  );
14
33
 
15
34
  const ResizablePanel = Panel;
@@ -23,7 +42,7 @@ const ResizableHandle = ({
23
42
  }) => (
24
43
  <Separator
25
44
  className={cn(
26
- "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
45
+ "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:-translate-y-1/2 aria-[orientation=horizontal]:after:translate-x-0 [&[aria-orientation=horizontal]>div]:rotate-90",
27
46
  className,
28
47
  )}
29
48
  {...props}
@@ -265,7 +265,12 @@ const SelectItem = React.forwardRef<React.ElementRef<typeof SelectPrimitive.Item
265
265
  <Check className="h-4 w-4" />
266
266
  </SelectPrimitive.ItemIndicator>
267
267
  </span>
268
- <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
268
+ <SelectPrimitive.ItemText>
269
+ {/* Wrap children in a flex row so an inline icon + label compose
270
+ * horizontally instead of stacking — Tailwind's preflight makes
271
+ * <svg> block-level, which would otherwise push the text below. */}
272
+ <span className="flex items-center gap-2">{children}</span>
273
+ </SelectPrimitive.ItemText>
269
274
  </SelectPrimitive.Item>
270
275
  ),
271
276
  );
@@ -5,6 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
5
5
  import { X } from "lucide-react";
6
6
  import * as React from "react";
7
7
 
8
+ import { usePortalContainer } from "../../lib/portal-container";
8
9
  import { cn } from "../../lib/utils";
9
10
 
10
11
  export interface SheetProps extends React.ComponentProps<typeof SheetPrimitive.Root> {
@@ -158,18 +159,25 @@ export interface SheetContentProps
158
159
  const SheetContent = React.forwardRef<
159
160
  React.ElementRef<typeof SheetPrimitive.Content>,
160
161
  SheetContentProps
161
- >(({ side = "right", className, children, ...props }, ref) => (
162
- <SheetPortal>
163
- <SheetOverlay />
164
- <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
165
- <SheetPrimitive.Close className="absolute right-4 top-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 data-[state=open]:bg-secondary">
166
- <X className="h-4 w-4" />
167
- <span className="sr-only">Close</span>
168
- </SheetPrimitive.Close>
169
- {children}
170
- </SheetPrimitive.Content>
171
- </SheetPortal>
172
- ));
162
+ >(({ side = "right", className, children, ...props }, ref) => {
163
+ const container = usePortalContainer();
164
+ return (
165
+ <SheetPortal container={container ?? undefined}>
166
+ <SheetOverlay />
167
+ <SheetPrimitive.Content
168
+ ref={ref}
169
+ className={cn(sheetVariants({ side }), className)}
170
+ {...props}
171
+ >
172
+ <SheetPrimitive.Close className="absolute right-4 top-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 data-[state=open]:bg-secondary">
173
+ <X className="h-4 w-4" />
174
+ <span className="sr-only">Close</span>
175
+ </SheetPrimitive.Close>
176
+ {children}
177
+ </SheetPrimitive.Content>
178
+ </SheetPortal>
179
+ );
180
+ });
173
181
  SheetContent.displayName = SheetPrimitive.Content.displayName;
174
182
 
175
183
  export interface SheetHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
package/src/index.ts CHANGED
@@ -364,6 +364,7 @@ export {
364
364
  DrawerContent,
365
365
  DrawerDescription,
366
366
  DrawerFooter,
367
+ DrawerHandle,
367
368
  DrawerHeader,
368
369
  DrawerOverlay,
369
370
  DrawerPortal,
package/styles/tokens.css CHANGED
@@ -39,7 +39,7 @@
39
39
  --muted-foreground: 240 3.8% 46.1%;
40
40
  --accent: 240 4.8% 95.9%;
41
41
  --accent-foreground: 240 5.9% 10%;
42
- --destructive: 0 84.2% 60.2%;
42
+ --destructive: 4 78% 50%;
43
43
  --destructive-foreground: 0 0% 98%;
44
44
  --brand: 213 94% 68%;
45
45
  --brand-foreground: 0 0% 100%;
@@ -77,7 +77,7 @@
77
77
  --stat-blue: 217 91% 60%; /* #3b82f6 */
78
78
  --stat-success: 160 84% 39%; /* #10b981 */
79
79
  --stat-purple: 258 90% 66%; /* #8b5cf6 */
80
- --stat-destructive: 0 84% 60%; /* #ef4444 */
80
+ --stat-destructive: 4 78% 50%; /* #df341d */
81
81
  --stat-amber: 38 92% 50%; /* #f59e0b */
82
82
 
83
83
  /* ── Typography ────────────────────────────────────────────── */
@@ -119,7 +119,7 @@
119
119
  --accent: 225 14% 22%;
120
120
  --accent-foreground: 220 14% 92%;
121
121
 
122
- --destructive: 0 70% 45%;
122
+ --destructive: 4 88% 62%;
123
123
  --destructive-foreground: 0 0% 98%;
124
124
  --brand: 213 94% 68%;
125
125
  --brand-foreground: 0 0% 100%;
@@ -151,7 +151,7 @@
151
151
  --stat-blue: 217 91% 60%;
152
152
  --stat-success: 160 84% 39%;
153
153
  --stat-purple: 258 90% 66%;
154
- --stat-destructive: 0 84% 60%;
154
+ --stat-destructive: 4 88% 62%;
155
155
  --stat-amber: 38 92% 50%;
156
156
  }
157
157
 
@@ -163,6 +163,45 @@
163
163
  background: hsl(var(--background));
164
164
  color: hsl(var(--foreground));
165
165
  }
166
+
167
+ /* ──────────────────────────────────────────────────────────────
168
+ * Themed scrollbars — applied globally so every overflow:auto /
169
+ * overflow:scroll element in canvas (and consumer apps that load
170
+ * tokens.css) gets canvas-token-tinted scrollbars instead of the
171
+ * stark OS defaults. WebKit (Chrome/Safari) uses pseudo-elements;
172
+ * Firefox uses the `scrollbar-*` properties. Both target the
173
+ * `--muted-foreground` token at 30%/45%/60% opacity for
174
+ * idle/hover/active so scrollbars stay subtle in dark mode and
175
+ * still legible in light mode.
176
+ * ────────────────────────────────────────────────────────────── */
177
+ * {
178
+ scrollbar-width: thin;
179
+ scrollbar-color: hsl(var(--muted-foreground) / 0.3) transparent;
180
+ }
181
+ *::-webkit-scrollbar {
182
+ width: 10px;
183
+ height: 10px;
184
+ }
185
+ *::-webkit-scrollbar-track {
186
+ background: transparent;
187
+ }
188
+ *::-webkit-scrollbar-thumb {
189
+ background-color: hsl(var(--muted-foreground) / 0.3);
190
+ border-radius: 6px;
191
+ /* Border creates the inset gap that makes the thumb feel like a
192
+ * floating pill rather than filling the gutter. */
193
+ border: 2px solid transparent;
194
+ background-clip: content-box;
195
+ }
196
+ *::-webkit-scrollbar-thumb:hover {
197
+ background-color: hsl(var(--muted-foreground) / 0.45);
198
+ }
199
+ *::-webkit-scrollbar-thumb:active {
200
+ background-color: hsl(var(--muted-foreground) / 0.6);
201
+ }
202
+ *::-webkit-scrollbar-corner {
203
+ background: transparent;
204
+ }
166
205
  }
167
206
 
168
207
  @custom-variant dark (&:is(.dark *));