Skip to content

Commit da13f1b

Browse files
sgammonclaude
andcommitted
refactor(ui): move non-component exports out of component files
A file that exports both a component and anything else (cva style objects, hooks) breaks Vite Fast Refresh — every edit full-reloads and wipes component state. Move the four cva variants to sibling *-variants.ts files and the useTheme/useMessages hooks next to their contexts (theme-context.ts, i18n/context.ts), repointing importers. The @elide/ui barrel re-exports everything from the new locations, so the public API is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1ecc9cb commit da13f1b

23 files changed

Lines changed: 155 additions & 116 deletions

packages/ui/src/components/ai-actions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from "react";
22
import { Check, Copy, ExternalLink, Sparkles } from "lucide-react";
33
import { cn } from "../lib/utils";
4-
import { useMessages } from "../i18n/provider";
4+
import { useMessages } from "../i18n/context";
55

66
/**
77
* AiActions — the "Use with AI" panel in the docs right rail: copy the page as

packages/ui/src/components/app-nav.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ChevronDown, Globe, History, Search, Sparkles, Sun } from "lucide-react
33
import { cn } from "../lib/utils";
44
import { Button } from "./button";
55
import { ElideLogo } from "./elide-logo";
6-
import { useMessages } from "../i18n/provider";
6+
import { useMessages } from "../i18n/context";
77

88
/**
99
* AppNav — the 56px top nav bar present on every docs page: brand + "DOCS"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { cva } from "class-variance-authority";
2+
3+
/**
4+
* Badge class variants. Kept out of badge.tsx so that file exports only
5+
* components and Fast Refresh can preserve state on edit.
6+
*/
7+
export const badgeVariants = cva(
8+
"inline-flex items-center gap-1.5 rounded-full font-semibold leading-none",
9+
{
10+
variants: {
11+
variant: {
12+
neutral: "border border-border text-muted-foreground",
13+
primary: "text-[var(--primary-emphasis)] [background:var(--primary-soft)]",
14+
supported: "text-[var(--eld-success-strong)] [background:color-mix(in_oklab,var(--eld-success-strong)_14%,transparent)]",
15+
partial: "text-[var(--eld-warning-strong)] [background:color-mix(in_oklab,var(--eld-warning-strong)_14%,transparent)]",
16+
missing: "text-muted-foreground [background:var(--muted)]",
17+
},
18+
size: {
19+
sm: "px-2 py-0.5 text-[10px]",
20+
md: "px-2.5 py-1 text-xs",
21+
},
22+
},
23+
defaultVariants: { variant: "neutral", size: "md" },
24+
},
25+
);
Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,12 @@
11
import * as React from "react";
2-
import { cva, type VariantProps } from "class-variance-authority";
2+
import { type VariantProps } from "class-variance-authority";
33
import { cn } from "../lib/utils";
4+
import { badgeVariants } from "./badge-variants";
45

56
/**
67
* Badge — leaf primitive. Includes the API-reference status tones
78
* (supported / partial / missing) used across the docs reference pages.
89
*/
9-
const badgeVariants = cva(
10-
"inline-flex items-center gap-1.5 rounded-full font-semibold leading-none",
11-
{
12-
variants: {
13-
variant: {
14-
neutral: "border border-border text-muted-foreground",
15-
primary: "text-[var(--primary-emphasis)] [background:var(--primary-soft)]",
16-
supported: "text-[var(--eld-success-strong)] [background:color-mix(in_oklab,var(--eld-success-strong)_14%,transparent)]",
17-
partial: "text-[var(--eld-warning-strong)] [background:color-mix(in_oklab,var(--eld-warning-strong)_14%,transparent)]",
18-
missing: "text-muted-foreground [background:var(--muted)]",
19-
},
20-
size: {
21-
sm: "px-2 py-0.5 text-[10px]",
22-
md: "px-2.5 py-1 text-xs",
23-
},
24-
},
25-
defaultVariants: { variant: "neutral", size: "md" },
26-
},
27-
);
28-
2910
export interface BadgeProps
3011
extends React.HTMLAttributes<HTMLSpanElement>,
3112
VariantProps<typeof badgeVariants> {
@@ -41,5 +22,3 @@ export function Badge({ className, variant, size, dot, children, ...props }: Bad
4122
</span>
4223
);
4324
}
44-
45-
export { badgeVariants };
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { cva } from "class-variance-authority";
2+
3+
/**
4+
* Button class variants. Kept out of button.tsx so that file exports only
5+
* components and Fast Refresh can preserve state on edit.
6+
*
7+
* `gradient` is the Elide brand CTA (the "Install" button). `changelog` is the
8+
* violet-outlined affordance used in the docs nav.
9+
*/
10+
export const buttonVariants = cva(
11+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
12+
{
13+
variants: {
14+
variant: {
15+
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
16+
gradient: "text-white [background:var(--eld-gradient-brand)] hover:opacity-95",
17+
outline: "border border-border bg-transparent text-foreground hover:bg-[var(--hover)]",
18+
ghost: "bg-transparent text-muted-foreground hover:bg-[var(--hover)] hover:text-foreground",
19+
changelog:
20+
"border text-foreground [border-color:var(--eld-accent-violet)] [background:color-mix(in_oklab,var(--eld-accent-violet)_10%,transparent)] [box-shadow:0_0_0_1px_color-mix(in_oklab,var(--eld-accent-violet)_22%,transparent),0_0_16px_-4px_color-mix(in_oklab,var(--eld-accent-violet)_55%,transparent)] hover:[background:color-mix(in_oklab,var(--eld-accent-violet)_16%,transparent)]",
21+
},
22+
size: {
23+
sm: "h-8 px-3 text-xs",
24+
md: "h-9 px-4 text-sm",
25+
icon: "h-9 w-9 p-0",
26+
"icon-sm": "h-8 w-8 p-0",
27+
},
28+
},
29+
defaultVariants: { variant: "primary", size: "md" },
30+
},
31+
);
Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,17 @@
11
import * as React from "react";
2-
import { cva, type VariantProps } from "class-variance-authority";
2+
import { type VariantProps } from "class-variance-authority";
33
import { cn } from "../lib/utils";
4+
import { buttonVariants } from "./button-variants";
45

56
/**
67
* Button — leaf primitive.
78
*
89
* Styled entirely from @elide/tokens (bg-primary, text-primary-foreground, …).
9-
* `gradient` is the Elide brand CTA (the "Install" button). `changelog` is the
10-
* violet-outlined affordance used in the docs nav.
1110
*
1211
* The `render` prop mirrors Base UI's composition model (replaces Radix
1312
* `asChild`): pass an element and the button's props/classes merge onto it —
1413
* e.g. `<Button render={<a href="/install" />}>Install</Button>`.
1514
*/
16-
const buttonVariants = cva(
17-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
18-
{
19-
variants: {
20-
variant: {
21-
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
22-
gradient: "text-white [background:var(--eld-gradient-brand)] hover:opacity-95",
23-
outline: "border border-border bg-transparent text-foreground hover:bg-[var(--hover)]",
24-
ghost: "bg-transparent text-muted-foreground hover:bg-[var(--hover)] hover:text-foreground",
25-
changelog:
26-
"border text-foreground [border-color:var(--eld-accent-violet)] [background:color-mix(in_oklab,var(--eld-accent-violet)_10%,transparent)] [box-shadow:0_0_0_1px_color-mix(in_oklab,var(--eld-accent-violet)_22%,transparent),0_0_16px_-4px_color-mix(in_oklab,var(--eld-accent-violet)_55%,transparent)] hover:[background:color-mix(in_oklab,var(--eld-accent-violet)_16%,transparent)]",
27-
},
28-
size: {
29-
sm: "h-8 px-3 text-xs",
30-
md: "h-9 px-4 text-sm",
31-
icon: "h-9 w-9 p-0",
32-
"icon-sm": "h-8 w-8 p-0",
33-
},
34-
},
35-
defaultVariants: { variant: "primary", size: "md" },
36-
},
37-
);
38-
3915
export interface ButtonProps
4016
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
4117
VariantProps<typeof buttonVariants> {
@@ -65,5 +41,3 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
6541
},
6642
);
6743
Button.displayName = "Button";
68-
69-
export { buttonVariants };

packages/ui/src/components/code-block.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as React from "react";
22
import { Check, Copy } from "lucide-react";
33
import { Highlight, type PrismTheme } from "prism-react-renderer";
44
import { cn, keyed } from "../lib/utils";
5-
import { useMessages } from "../i18n/provider";
5+
import { useMessages } from "../i18n/context";
66

77
/**
88
* CopyButton — copies `value` to the clipboard, flips to a check for 1.5s.

packages/ui/src/components/mobile-nav.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { History, Menu, Search, Sparkle } from "lucide-react";
33
import { cn } from "../lib/utils";
44
import { Button } from "./button";
55
import { Sheet, SheetTrigger, SheetContent } from "./sheet";
6-
import { useMessages } from "../i18n/provider";
6+
import { useMessages } from "../i18n/context";
77

88
/**
99
* MobileNav — the docs shell at phone width (mockup 3b): a condensed top app
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { cva } from "class-variance-authority"
2+
3+
/**
4+
* Trigger style shared by NavigationMenuTrigger and standalone links styled to
5+
* match. Kept out of navigation-menu.tsx so that file exports only components
6+
* and Fast Refresh can preserve state on edit.
7+
*/
8+
export const navigationMenuTriggerStyle = cva(
9+
"group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center rounded-lg px-2.5 py-1.5 text-sm font-medium transition-all outline-none hover:bg-muted focus:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-muted/50 data-popup-open:hover:bg-muted data-open:bg-muted/50 data-open:hover:bg-muted data-open:focus:bg-muted"
10+
)

packages/ui/src/components/navigation-menu.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu"
2-
import { cva } from "class-variance-authority"
32

43
import { cn } from "../lib/utils"
4+
import { navigationMenuTriggerStyle } from "./navigation-menu-variants"
55
import { ChevronDownIcon } from "lucide-react"
66

77
function NavigationMenu({
@@ -55,10 +55,6 @@ function NavigationMenuItem({
5555
)
5656
}
5757

58-
const navigationMenuTriggerStyle = cva(
59-
"group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center rounded-lg px-2.5 py-1.5 text-sm font-medium transition-all outline-none hover:bg-muted focus:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-muted/50 data-popup-open:hover:bg-muted data-open:bg-muted/50 data-open:hover:bg-muted data-open:focus:bg-muted"
60-
)
61-
6258
function NavigationMenuTrigger({
6359
className,
6460
children,
@@ -163,6 +159,5 @@ export {
163159
NavigationMenuLink,
164160
NavigationMenuList,
165161
NavigationMenuTrigger,
166-
navigationMenuTriggerStyle,
167162
NavigationMenuPositioner,
168163
}

0 commit comments

Comments
 (0)