@camox/ui 0.6.1 → 0.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.
@@ -1,23 +1,27 @@
1
- import * as SwitchPrimitive from "@radix-ui/react-switch";
2
- import * as React from "react";
1
+ import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
3
2
 
4
3
  import { cn } from "../lib/utils";
5
4
 
6
- function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
5
+ function Switch({
6
+ className,
7
+ size = "default",
8
+ ...props
9
+ }: SwitchPrimitive.Root.Props & {
10
+ size?: "sm" | "default";
11
+ }) {
7
12
  return (
8
13
  <SwitchPrimitive.Root
9
14
  data-slot="switch"
15
+ data-size={size}
10
16
  className={cn(
11
- "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
17
+ "peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
12
18
  className,
13
19
  )}
14
20
  {...props}
15
21
  >
16
22
  <SwitchPrimitive.Thumb
17
23
  data-slot="switch-thumb"
18
- className={cn(
19
- "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0",
20
- )}
24
+ className="bg-background dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0"
21
25
  />
22
26
  </SwitchPrimitive.Root>
23
27
  );
@@ -1,37 +1,58 @@
1
- import * as TabsPrimitive from "@radix-ui/react-tabs";
2
- import * as React from "react";
1
+ import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
3
 
4
4
  import { cn } from "../lib/utils";
5
5
 
6
- function Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) {
6
+ function Tabs({ className, orientation = "horizontal", ...props }: TabsPrimitive.Root.Props) {
7
7
  return (
8
8
  <TabsPrimitive.Root
9
9
  data-slot="tabs"
10
- className={cn("flex flex-col gap-2", className)}
10
+ data-orientation={orientation}
11
+ className={cn("group/tabs flex gap-2 data-horizontal:flex-col", className)}
11
12
  {...props}
12
13
  />
13
14
  );
14
15
  }
15
16
 
16
- function TabsList({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.List>) {
17
+ const tabsListVariants = cva(
18
+ "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
19
+ {
20
+ variants: {
21
+ variant: {
22
+ default: "bg-muted",
23
+ line: "gap-1 bg-transparent",
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: "default",
28
+ },
29
+ },
30
+ );
31
+
32
+ function TabsList({
33
+ className,
34
+ variant = "default",
35
+ ...props
36
+ }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
17
37
  return (
18
38
  <TabsPrimitive.List
19
39
  data-slot="tabs-list"
20
- className={cn(
21
- "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
22
- className,
23
- )}
40
+ data-variant={variant}
41
+ className={cn(tabsListVariants({ variant }), className)}
24
42
  {...props}
25
43
  />
26
44
  );
27
45
  }
28
46
 
29
- function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
47
+ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
30
48
  return (
31
- <TabsPrimitive.Trigger
49
+ <TabsPrimitive.Tab
32
50
  data-slot="tabs-trigger"
33
51
  className={cn(
34
- "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
52
+ "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start 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.5 has-data-[icon=inline-start]:pl-1.5 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
53
+ "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",
54
+ "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
55
+ "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",
35
56
  className,
36
57
  )}
37
58
  {...props}
@@ -39,14 +60,14 @@ function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPr
39
60
  );
40
61
  }
41
62
 
42
- function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {
63
+ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
43
64
  return (
44
- <TabsPrimitive.Content
65
+ <TabsPrimitive.Panel
45
66
  data-slot="tabs-content"
46
- className={cn("flex-1 outline-none", className)}
67
+ className={cn("flex-1 text-sm outline-none", className)}
47
68
  {...props}
48
69
  />
49
70
  );
50
71
  }
51
72
 
52
- export { Tabs, TabsList, TabsTrigger, TabsContent };
73
+ export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
@@ -1,15 +1,13 @@
1
1
  import * as React from "react";
2
2
 
3
- import { INPUT_BASE_STYLES, INPUT_FOCUS_STYLES, cn } from "../lib/utils";
3
+ import { cn } from "../lib/utils";
4
4
 
5
5
  function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6
6
  return (
7
7
  <textarea
8
8
  data-slot="textarea"
9
9
  className={cn(
10
- INPUT_BASE_STYLES,
11
- INPUT_FOCUS_STYLES,
12
- "dark:bg-input/30 flex field-sizing-content min-h-16 w-full px-3 py-2",
10
+ "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
13
11
  className,
14
12
  )}
15
13
  {...props}
@@ -1,23 +1,21 @@
1
- import * as TogglePrimitive from "@radix-ui/react-toggle";
2
- import type { VariantProps } from "class-variance-authority";
3
- import { cva } from "class-variance-authority";
4
- import * as React from "react";
1
+ import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
2
+ import { cva, type VariantProps } from "class-variance-authority";
5
3
 
6
4
  import { cn } from "../lib/utils";
7
5
 
8
6
  const toggleVariants = cva(
9
- "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
7
+ "group/toggle inline-flex items-center justify-center gap-1 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
10
8
  {
11
9
  variants: {
12
10
  variant: {
13
11
  default: "bg-transparent",
14
- outline:
15
- "border border-input bg-transparent shadow-xs hover:bg-accent/50 hover:text-accent-foreground",
12
+ outline: "border border-input bg-transparent shadow-xs hover:bg-muted",
16
13
  },
17
14
  size: {
18
- default: "h-9 px-2 min-w-9",
19
- sm: "h-8 px-1.5 min-w-8",
20
- lg: "h-10 px-2.5 min-w-10",
15
+ default:
16
+ "h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
17
+ sm: "h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
18
+ lg: "h-10 min-w-10 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
21
19
  },
22
20
  },
23
21
  defaultVariants: {
@@ -29,12 +27,12 @@ const toggleVariants = cva(
29
27
 
30
28
  function Toggle({
31
29
  className,
32
- variant,
33
- size,
30
+ variant = "default",
31
+ size = "default",
34
32
  ...props
35
- }: React.ComponentProps<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>) {
33
+ }: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
36
34
  return (
37
- <TogglePrimitive.Root
35
+ <TogglePrimitive
38
36
  data-slot="toggle"
39
37
  className={cn(toggleVariants({ variant, size, className }))}
40
38
  {...props}
@@ -1,53 +1,50 @@
1
- import * as TooltipPrimitive from "@radix-ui/react-tooltip";
2
- import * as React from "react";
1
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
3
2
 
4
3
  import { cn } from "../lib/utils";
5
4
 
6
- function TooltipProvider({
7
- delayDuration = 0,
8
- ...props
9
- }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
10
- return (
11
- <TooltipPrimitive.Provider
12
- data-slot="tooltip-provider"
13
- delayDuration={delayDuration}
14
- {...props}
15
- />
16
- );
5
+ function TooltipProvider({ delay = 0, ...props }: TooltipPrimitive.Provider.Props) {
6
+ return <TooltipPrimitive.Provider data-slot="tooltip-provider" delay={delay} {...props} />;
17
7
  }
18
8
 
19
- function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
20
- return (
21
- <TooltipProvider>
22
- <TooltipPrimitive.Root data-slot="tooltip" {...props} />
23
- </TooltipProvider>
24
- );
9
+ function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
10
+ return <TooltipPrimitive.Root data-slot="tooltip" {...props} />;
25
11
  }
26
12
 
27
- function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
13
+ function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
28
14
  return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
29
15
  }
30
16
 
31
17
  function TooltipContent({
32
18
  className,
33
- sideOffset = 0,
19
+ side = "top",
20
+ sideOffset = 4,
21
+ align = "center",
22
+ alignOffset = 0,
34
23
  children,
35
24
  ...props
36
- }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
25
+ }: TooltipPrimitive.Popup.Props &
26
+ Pick<TooltipPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">) {
37
27
  return (
38
28
  <TooltipPrimitive.Portal>
39
- <TooltipPrimitive.Content
40
- data-slot="tooltip-content"
29
+ <TooltipPrimitive.Positioner
30
+ align={align}
31
+ alignOffset={alignOffset}
32
+ side={side}
41
33
  sideOffset={sideOffset}
42
- className={cn(
43
- "bg-foreground text-background 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-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-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
44
- className,
45
- )}
46
- {...props}
34
+ className="isolate z-50"
47
35
  >
48
- {children}
49
- <TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
50
- </TooltipPrimitive.Content>
36
+ <TooltipPrimitive.Popup
37
+ data-slot="tooltip-content"
38
+ className={cn(
39
+ "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md 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-sm 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",
40
+ className,
41
+ )}
42
+ {...props}
43
+ >
44
+ {children}
45
+ <TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] 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" />
46
+ </TooltipPrimitive.Popup>
47
+ </TooltipPrimitive.Positioner>
51
48
  </TooltipPrimitive.Portal>
52
49
  );
53
50
  }
@@ -42,7 +42,7 @@
42
42
  --foreground: theme(--color-zinc-50);
43
43
  --card: theme(--color-zinc-900);
44
44
  --card-foreground: theme(--color-zinc-50);
45
- --popover: theme(--color-zinc-900);
45
+ --popover: oklch(17.5% 0.0055 285.85);
46
46
  --popover-foreground: theme(--color-zinc-50);
47
47
  --primary: theme(--color-emerald-600);
48
48
  --primary-foreground: theme(--color-emerald-50);
@@ -1,58 +0,0 @@
1
- import * as AccordionPrimitive from "@radix-ui/react-accordion";
2
- import * as React from "react";
3
-
4
- import { cn } from "../lib/utils";
5
-
6
- function Accordion({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
7
- return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
8
- }
9
-
10
- function AccordionItem({
11
- className,
12
- ...props
13
- }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
14
- return (
15
- <AccordionPrimitive.Item
16
- data-slot="accordion-item"
17
- className={cn("border-b last:border-b-0", className)}
18
- {...props}
19
- />
20
- );
21
- }
22
-
23
- interface AccordionTriggerProps extends React.ComponentProps<typeof AccordionPrimitive.Trigger> {}
24
-
25
- function AccordionTrigger({ className, children, ...props }: AccordionTriggerProps) {
26
- return (
27
- <AccordionPrimitive.Header className="flex">
28
- <AccordionPrimitive.Trigger
29
- data-slot="accordion-trigger"
30
- className={cn(
31
- "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
32
- className,
33
- )}
34
- {...props}
35
- >
36
- {children}
37
- </AccordionPrimitive.Trigger>
38
- </AccordionPrimitive.Header>
39
- );
40
- }
41
-
42
- function AccordionContent({
43
- className,
44
- children,
45
- ...props
46
- }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
47
- return (
48
- <AccordionPrimitive.Content
49
- data-slot="accordion-content"
50
- className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
51
- {...props}
52
- >
53
- <div className={cn("pt-0 pb-4", className)}>{children}</div>
54
- </AccordionPrimitive.Content>
55
- );
56
- }
57
-
58
- export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
@@ -1,27 +0,0 @@
1
- import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
2
- import { CheckIcon } from "lucide-react";
3
- import * as React from "react";
4
-
5
- import { cn } from "../lib/utils";
6
-
7
- function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
8
- return (
9
- <CheckboxPrimitive.Root
10
- data-slot="checkbox"
11
- className={cn(
12
- "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
13
- className,
14
- )}
15
- {...props}
16
- >
17
- <CheckboxPrimitive.Indicator
18
- data-slot="checkbox-indicator"
19
- className="flex items-center justify-center text-current transition-none"
20
- >
21
- <CheckIcon className="size-3.5" />
22
- </CheckboxPrimitive.Indicator>
23
- </CheckboxPrimitive.Root>
24
- );
25
- }
26
-
27
- export { Checkbox };
@@ -1,58 +0,0 @@
1
- "use client";
2
-
3
- import { Primitive } from "@radix-ui/react-primitive";
4
- import { Slot } from "@radix-ui/react-slot";
5
- import * as React from "react";
6
-
7
- import { cn } from "../lib/utils";
8
-
9
- const ControlGroupContext = React.createContext<Pick<ControlGroupProps, "orientation">>({
10
- orientation: "horizontal",
11
- });
12
-
13
- function useControlGroup() {
14
- const context = React.useContext(ControlGroupContext);
15
- if (!context) {
16
- throw new Error("useControlGroup must be used within a <ControlGroup />.");
17
- }
18
-
19
- return context;
20
- }
21
-
22
- export interface ControlGroupProps extends React.ComponentProps<typeof Primitive.div> {
23
- orientation?: "horizontal" | "vertical";
24
- }
25
-
26
- function ControlGroup({ className, orientation = "horizontal", ...props }: ControlGroupProps) {
27
- return (
28
- <ControlGroupContext.Provider value={{ orientation }}>
29
- <Primitive.div
30
- data-slot="control-group"
31
- data-orientation={orientation}
32
- className={cn("flex", orientation === "vertical" && "flex-col", className)}
33
- {...props}
34
- />
35
- </ControlGroupContext.Provider>
36
- );
37
- }
38
-
39
- function ControlGroupItem({ className, ...props }: React.ComponentProps<typeof Slot>) {
40
- const { orientation } = useControlGroup();
41
-
42
- return (
43
- <Slot
44
- data-slot="control-group-item"
45
- className={cn(
46
- "rounded-none focus-within:z-10",
47
- orientation === "horizontal" &&
48
- "-me-px h-auto first:rounded-s-md last:-me-0 last:rounded-e-md",
49
- orientation === "vertical" &&
50
- "w-auto [margin-block-end:-1px] first:rounded-ss-md first:rounded-se-md last:rounded-ee-md last:rounded-es-md last:[margin-block-end:0]",
51
- className,
52
- )}
53
- {...props}
54
- />
55
- );
56
- }
57
-
58
- export { ControlGroup, ControlGroupItem };
@@ -1,146 +0,0 @@
1
- import * as React from "react";
2
- import { createPortal } from "react-dom";
3
-
4
- import { cn } from "../lib/utils";
5
-
6
- interface FrameContextValue {
7
- window: Window | null;
8
- iframeElement: HTMLIFrameElement | null;
9
- }
10
-
11
- const FrameContext = React.createContext<FrameContextValue>({
12
- window: null,
13
- iframeElement: null,
14
- });
15
-
16
- export function useFrame() {
17
- const context = React.use(FrameContext);
18
- if (!context) {
19
- throw new Error("useFrame must be used within a Frame");
20
- }
21
- return context;
22
- }
23
-
24
- interface FrameProps {
25
- children: React.ReactNode;
26
- /** Optional className for the iframe element */
27
- className?: string;
28
- /** Optional inline styles for the iframe element */
29
- style?: React.CSSProperties;
30
- /** Whether to copy parent document styles into the iframe (default: true) */
31
- copyStyles?: boolean;
32
- /** Callback when iframe is ready, receives the iframe element */
33
- onIframeReady?: (iframe: HTMLIFrameElement) => void;
34
- }
35
-
36
- export const Frame = ({
37
- children,
38
- className,
39
- style,
40
- copyStyles = true,
41
- onIframeReady,
42
- }: FrameProps) => {
43
- const iframeRef = React.useRef<HTMLIFrameElement>(null);
44
- const [iframeWindow, setIframeWindow] = React.useState<Window | null>(null);
45
- const [iframeElement, setIframeElement] = React.useState<HTMLIFrameElement | null>(null);
46
- const [mountNode, setMountNode] = React.useState<HTMLElement | null>(null);
47
- const [hasRadixPopper, setHasRadixPopper] = React.useState(false);
48
-
49
- React.useEffect(() => {
50
- const iframe = iframeRef.current;
51
- if (!iframe) return;
52
-
53
- const handleLoad = () => {
54
- const iframeDoc = iframe.contentDocument;
55
- const iframeWin = iframe.contentWindow;
56
-
57
- if (!iframeDoc || !iframeWin) return;
58
-
59
- // Set up basic document structure
60
- iframeDoc.open();
61
- iframeDoc.write(
62
- "<!DOCTYPE html><html><head></head><body style='background: transparent;'></body></html>",
63
- );
64
- iframeDoc.close();
65
-
66
- // Navigate the top-level window when a native <a> is clicked inside the
67
- // iframe. Links managed by a client-side router (e.g. TanStack Router's
68
- // <Link>) call e.preventDefault() themselves, so we skip those.
69
- // We listen on `iframeWin` (not `iframeDoc`) so that this handler fires
70
- // AFTER React's event delegation (which is on the document/body), giving
71
- // React a chance to call preventDefault() first.
72
- iframeWin.addEventListener("click", (e) => {
73
- if (e.defaultPrevented) return;
74
- const anchor = (e.target as Element).closest("a");
75
- if (!anchor?.href) return;
76
- if (anchor.target === "_blank") return;
77
- e.preventDefault();
78
- window.top?.location.assign(anchor.href);
79
- });
80
-
81
- // Copy styles from parent document if requested
82
- if (copyStyles) {
83
- const headStyles = Array.from(
84
- document.head.querySelectorAll('style, link[rel="stylesheet"]'),
85
- );
86
- headStyles.forEach((style) => {
87
- const clonedStyle = style.cloneNode(true);
88
- iframeDoc.head.appendChild(clonedStyle);
89
- });
90
- }
91
-
92
- // Set the mount node to the iframe's body
93
- setMountNode(iframeDoc.body);
94
- setIframeWindow(iframeWin);
95
- setIframeElement(iframe);
96
- onIframeReady?.(iframe);
97
- };
98
-
99
- // Add load event listener
100
- iframe.addEventListener("load", handleLoad);
101
-
102
- // Trigger load if iframe is already loaded
103
- if (iframe.contentDocument?.readyState === "complete") {
104
- handleLoad();
105
- }
106
-
107
- return () => {
108
- iframe.removeEventListener("load", handleLoad);
109
- };
110
- }, [copyStyles, onIframeReady]);
111
-
112
- // Monitor for Radix popper content wrapper in body
113
- React.useEffect(() => {
114
- const checkForRadixPopper = () => {
115
- const hasPopper =
116
- document.body.querySelector(":scope > [data-radix-popper-content-wrapper]") !== null;
117
- setHasRadixPopper(hasPopper);
118
- };
119
-
120
- // Initial check
121
- checkForRadixPopper();
122
-
123
- // Set up MutationObserver to watch for changes
124
- const observer = new MutationObserver(checkForRadixPopper);
125
- observer.observe(document.body, {
126
- childList: true,
127
- subtree: false, // Only watch direct children of body
128
- });
129
-
130
- return () => {
131
- observer.disconnect();
132
- };
133
- }, []);
134
-
135
- return (
136
- <div className={cn("relative w-full h-full", className)} style={style}>
137
- {/* Display an overlay to properly close close Radix portals (modals, popovers...) */}
138
- {/* because otherwise Radix wouldn't detect pointer events that would happen on the iframe */}
139
- {hasRadixPopper && <div className="absolute top-0 left-0 h-full w-full" />}
140
- <FrameContext.Provider value={{ window: iframeWindow, iframeElement }}>
141
- <iframe ref={iframeRef} className={cn("w-full h-full")} />
142
- {mountNode && createPortal(children, mountNode)}
143
- </FrameContext.Provider>
144
- </div>
145
- );
146
- };