@ponti-studios/ui 0.7.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +13 -0
  2. package/package.json +22 -20
  3. package/src/components/data-display/accordion.tsx +1 -1
  4. package/src/components/data-display/sort-controls.tsx +3 -3
  5. package/src/components/feedback/index.ts +0 -2
  6. package/src/components/forms/date-month-select.tsx +12 -3
  7. package/src/components/forms/date-picker.tsx +16 -12
  8. package/src/components/forms/drop-zone.tsx +1 -1
  9. package/src/components/forms/index.ts +4 -8
  10. package/src/components/forms/passkey-management.tsx +4 -10
  11. package/src/components/forms/radio-group.tsx +8 -4
  12. package/src/components/forms/select.tsx +1 -1
  13. package/src/components/forms/text-field.tsx +40 -5
  14. package/src/components/navigation/app-navigation.stories.tsx +111 -21
  15. package/src/components/navigation/app-navigation.tsx +165 -137
  16. package/src/components/navigation/index.ts +8 -4
  17. package/src/components/navigation/pagination-controls.stories.tsx +1 -1
  18. package/src/components/navigation/pagination-controls.tsx +10 -14
  19. package/src/components/overlays/dropdown-menu.stories.tsx +2 -2
  20. package/src/components/primitives/card.tsx +6 -10
  21. package/src/components/primitives/label.stories.tsx +0 -18
  22. package/src/components/primitives/label.tsx +12 -12
  23. package/src/constants/chart-colors.ts +16 -16
  24. package/src/lib/slot.tsx +29 -0
  25. package/src/styles/animations.css +0 -22
  26. package/src/styles/index.css +60 -6
  27. package/src/styles/tokens/color-systems.stories.tsx +2 -1
  28. package/src/styles/tokens/generated/light.colors.generated.ts +3 -3
  29. package/src/styles/tokens/generated/light.css +3 -3
  30. package/src/styles/tokens/source/light.tokens.json +3 -3
  31. package/src/styles/tokens/style-dictionary.light-storybook.json +16 -0
  32. package/src/components/feedback/file-upload-status-badge.stories.tsx +0 -28
  33. package/src/components/feedback/file-upload-status-badge.tsx +0 -33
  34. package/src/components/forms/field.stories.tsx +0 -62
  35. package/src/components/forms/field.tsx +0 -65
  36. package/src/components/forms/field.types.ts +0 -7
  37. package/src/components/forms/group-by-select.stories.tsx +0 -26
  38. package/src/components/forms/group-by-select.tsx +0 -47
  39. package/src/components/navigation/route-link.stories.tsx +0 -30
  40. package/src/components/navigation/route-link.tsx +0 -42
  41. package/src/hooks/use-derived-filter-state.stories.tsx +0 -136
  42. package/src/hooks/use-filter-state.stories.tsx +0 -102
@@ -1,155 +1,183 @@
1
1
  import { Menu } from "lucide-react";
2
+ import type { ComponentPropsWithoutRef, ReactNode } from "react";
2
3
  import * as React from "react";
3
4
 
4
- import { Button } from "../primitives/button";
5
+ import { cn } from "../../lib/utils";
6
+ import { Slot } from "../../lib/slot";
5
7
  import { Sheet, SheetClose, SheetContent, SheetTitle, SheetTrigger } from "../overlays/sheet";
8
+ import { Button } from "../primitives/button";
6
9
 
7
- export interface AppNavigationLink {
8
- href: string;
9
- label: string;
10
- }
11
-
12
- export interface AppNavigationCta {
13
- href: string;
14
- label: string;
15
- variant?: "default" | "outline";
16
- }
10
+ /** True while rendering inside the mobile Sheet copy of the nav content. */
11
+ const MobileMenuContext = React.createContext(false);
17
12
 
18
- export interface AppNavigationProps {
19
- brand?: React.ReactNode;
20
- brandHref?: string;
21
- endContent?: React.ReactNode;
22
- links?: AppNavigationLink[];
23
- cta?: AppNavigationCta;
13
+ export interface NavigationRootProps {
14
+ children: ReactNode;
24
15
  ariaLabel?: string;
25
- /** Current pathname used to highlight the active link. */
26
- activeHref?: string;
27
- /** A router link component. Native anchors are used when omitted. */
28
- linkComponent?: React.ElementType;
29
- /** The prop used by the link component for its destination. */
30
- linkProp?: "href" | "to";
31
- /** Props shared by every link, such as a router's prefetch hint. */
32
- linkProps?: Record<string, unknown>;
16
+ className?: string;
33
17
  }
34
18
 
35
- export function AppNavigation({
36
- brand,
37
- brandHref = "/",
38
- endContent,
39
- links,
40
- cta,
41
- ariaLabel = "Primary navigation",
42
- activeHref,
43
- linkComponent: LinkComponent = "a",
44
- linkProp = "href",
45
- linkProps,
46
- }: AppNavigationProps) {
19
+ /**
20
+ * Sticky nav shell. Compose it with `Navigation.Brand`, `Navigation.List`
21
+ * (containing `Navigation.Item`s), and any other content — a CTA `Button`,
22
+ * a search input, a notification bell — as plain children. `Navigation`
23
+ * only owns the responsive chrome: `Navigation.Brand` stays visible at
24
+ * every width, everything else collapses into a Sheet-based mobile menu
25
+ * below `sm`, reusing the exact elements you composed (not a parallel data
26
+ * shape) so `Navigation.Item`'s active state and `asChild` composition
27
+ * behave identically in both places.
28
+ */
29
+ function NavigationRoot({ children, ariaLabel = "Primary navigation", className }: NavigationRootProps) {
47
30
  const [mobileOpen, setMobileOpen] = React.useState(false);
48
31
 
49
- const isActive = (href: string) => {
50
- if (!activeHref) return false;
51
- if (href === activeHref) return true;
52
- // Nested routes (e.g. /work/123) keep the parent link active.
53
- return href !== "/" && activeHref.startsWith(`${href}/`);
54
- };
55
-
56
- const createLink = ({
57
- href,
58
- children,
59
- active = false,
60
- variant,
61
- brand = false,
62
- }: {
63
- href: string;
64
- children: React.ReactNode;
65
- active?: boolean;
66
- variant?: AppNavigationCta["variant"];
67
- brand?: boolean;
68
- }) =>
69
- React.createElement(
70
- LinkComponent,
71
- {
72
- ...linkProps,
73
- ...(linkProp === "to" ? { to: href } : { href }),
74
- className: brand
75
- ? "inline-flex min-h-9 shrink-0 items-center rounded-md px-3 py-2 text-sm font-medium"
76
- : variant
77
- ? "inline-flex min-h-9 shrink-0 items-center justify-center rounded-md px-3 py-2 text-sm font-medium"
78
- : "inline-flex min-h-9 shrink-0 items-center rounded-md px-3 py-2 text-sm font-medium hover:bg-muted hover:text-muted-foreground",
79
- "aria-current": active ? "page" : undefined,
80
- "data-variant": variant,
81
- },
82
- children,
83
- );
32
+ const items = React.Children.toArray(children);
33
+ const brand = items.find((child) => React.isValidElement(child) && child.type === NavigationBrand);
34
+ const rest = items.filter((child) => child !== brand);
84
35
 
85
36
  return (
86
- <header className="bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 flex w-full justify-center border-b px-4 backdrop-blur">
87
- <nav className="w-full max-w-7xl" aria-label={ariaLabel}>
88
- <div className="flex min-h-14 items-center justify-between gap-6">
89
- <div className="flex min-w-0 items-center">
90
- {brand && createLink({ href: brandHref, children: brand, brand: true })}
91
- </div>
92
-
93
- <div className="ml-auto hidden min-w-0 items-center gap-1 sm:flex">
94
- {links?.map((link) => (
95
- <React.Fragment key={link.href}>
96
- {createLink({ href: link.href, children: link.label, active: isActive(link.href) })}
97
- </React.Fragment>
98
- ))}
99
- {cta &&
100
- createLink({
101
- href: cta.href,
102
- children: cta.label,
103
- variant: cta.variant ?? "default",
104
- })}
105
- {endContent}
106
- </div>
107
-
108
- <div className="ml-auto flex items-center sm:hidden">
109
- <Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
110
- <SheetTrigger asChild>
111
- <Button
112
- variant="outline"
113
- size="icon"
114
- aria-label={mobileOpen ? "Close menu" : "Open menu"}
115
- >
116
- <Menu className="size-4" aria-hidden="true" />
117
- </Button>
118
- </SheetTrigger>
119
- <SheetContent>
120
- <SheetTitle className="sr-only">Navigation menu</SheetTitle>
121
-
122
- <div className="mt-8 flex flex-1 flex-col gap-1">
123
- {links?.map((link) => (
124
- <SheetClose asChild key={link.href}>
125
- {createLink({
126
- href: link.href,
127
- children: link.label,
128
- active: isActive(link.href),
129
- })}
130
- </SheetClose>
131
- ))}
132
- </div>
133
-
134
- {(cta || endContent) && (
135
- <div className="flex flex-col gap-4 border-t pt-4">
136
- {cta && (
137
- <SheetClose asChild>
138
- {createLink({
139
- href: cta.href,
140
- children: cta.label,
141
- variant: cta.variant ?? "default",
142
- })}
143
- </SheetClose>
144
- )}
145
- {endContent && <div className="flex items-center gap-3">{endContent}</div>}
146
- </div>
147
- )}
148
- </SheetContent>
149
- </Sheet>
150
- </div>
37
+ <header className="bg-background/95 supports-backdrop-filter:bg-background/60 sticky top-0 z-50 flex w-full justify-center border-b px-4 backdrop-blur">
38
+ <nav
39
+ className={cn("flex min-h-14 w-full max-w-7xl items-center justify-between gap-6", className)}
40
+ aria-label={ariaLabel}
41
+ >
42
+ <div className="flex min-w-0 items-center">{brand}</div>
43
+
44
+ <div className="ml-auto hidden min-w-0 items-center gap-6 sm:flex">{rest}</div>
45
+
46
+ <div className="ml-auto flex items-center sm:hidden">
47
+ <Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
48
+ <SheetTrigger asChild>
49
+ <Button
50
+ variant="outline"
51
+ size="icon"
52
+ aria-label={mobileOpen ? "Close menu" : "Open menu"}
53
+ >
54
+ <Menu className="size-4" aria-hidden="true" />
55
+ </Button>
56
+ </SheetTrigger>
57
+ <SheetContent>
58
+ <SheetTitle className="sr-only">Navigation menu</SheetTitle>
59
+ <div className="mt-8 flex flex-1 flex-col gap-4">
60
+ <MobileMenuContext.Provider value={true}>{rest}</MobileMenuContext.Provider>
61
+ </div>
62
+ </SheetContent>
63
+ </Sheet>
151
64
  </div>
152
65
  </nav>
153
66
  </header>
154
67
  );
155
68
  }
69
+
70
+ export interface NavigationBrandProps {
71
+ children: ReactNode;
72
+ className?: string;
73
+ }
74
+
75
+ /** Always visible, at every viewport width — compose your own link/logo markup directly. */
76
+ function NavigationBrand({ children, className }: NavigationBrandProps) {
77
+ return <div className={cn("flex items-center font-semibold", className)}>{children}</div>;
78
+ }
79
+
80
+ export interface NavigationListProps {
81
+ children: ReactNode;
82
+ className?: string;
83
+ }
84
+
85
+ /** Semantic `<ul>` of `Navigation.Item`s. Lays out horizontally on desktop, stacks inside the mobile Sheet. */
86
+ function NavigationList({ children, className }: NavigationListProps) {
87
+ const isMobile = React.useContext(MobileMenuContext);
88
+ return (
89
+ <ul
90
+ className={cn(
91
+ "flex items-center gap-1",
92
+ isMobile && "flex-col items-stretch gap-1",
93
+ className,
94
+ )}
95
+ >
96
+ {children}
97
+ </ul>
98
+ );
99
+ }
100
+
101
+ interface NavigationItemOwnProps {
102
+ active?: boolean;
103
+ /** Apply the item styling to a single child (e.g. your router's `Link`) instead of rendering an `<a>`. */
104
+ asChild?: boolean;
105
+ }
106
+
107
+ export type NavigationItemProps = NavigationItemOwnProps &
108
+ Omit<ComponentPropsWithoutRef<"a">, keyof NavigationItemOwnProps>;
109
+
110
+ /** A single nav link, wrapped in `<li>`. Auto-closes the mobile Sheet on click when rendered there. */
111
+ function NavigationItem({ active = false, asChild = false, className, children, ...props }: NavigationItemProps) {
112
+ const isMobile = React.useContext(MobileMenuContext);
113
+ const itemClassName = cn(
114
+ "inline-flex h-9 max-h-9 shrink-0 items-center px-3 py-2 text-sm font-medium hover:bg-muted hover:text-muted-foreground data-[active]:font-semibold data-[active]:text-foreground",
115
+ className,
116
+ );
117
+
118
+ const content = asChild ? (
119
+ <Slot
120
+ {...props}
121
+ data-active={active || undefined}
122
+ aria-current={active ? "page" : undefined}
123
+ className={itemClassName}
124
+ >
125
+ {children}
126
+ </Slot>
127
+ ) : (
128
+ <a
129
+ {...props}
130
+ data-active={active || undefined}
131
+ aria-current={active ? "page" : undefined}
132
+ className={itemClassName}
133
+ >
134
+ {children}
135
+ </a>
136
+ );
137
+
138
+ return <li>{isMobile ? <SheetClose asChild>{content}</SheetClose> : content}</li>;
139
+ }
140
+
141
+ export interface NavigationActionProps {
142
+ children: ReactNode;
143
+ className?: string;
144
+ }
145
+
146
+ /**
147
+ * Layout wrapper for a CTA, avatar menu, theme toggle, etc. — bring your
148
+ * own `Button`, no dedicated CTA component needed. Content here does not
149
+ * auto-close the mobile Sheet on click (it may hold more than one
150
+ * element, or non-navigational controls) — use `Navigation.Item` instead
151
+ * for a link that should close the menu when tapped.
152
+ */
153
+ function NavigationAction({ children, className }: NavigationActionProps) {
154
+ const isMobile = React.useContext(MobileMenuContext);
155
+ return (
156
+ <div
157
+ className={cn(
158
+ "flex items-center gap-3",
159
+ isMobile && "mt-2 flex-col items-stretch gap-3 border-t pt-4",
160
+ className,
161
+ )}
162
+ >
163
+ {children}
164
+ </div>
165
+ );
166
+ }
167
+
168
+ type NavigationComponent = typeof NavigationRoot & {
169
+ Brand: typeof NavigationBrand;
170
+ List: typeof NavigationList;
171
+ Item: typeof NavigationItem;
172
+ Action: typeof NavigationAction;
173
+ };
174
+
175
+ export const Navigation: NavigationComponent = Object.assign(NavigationRoot, {
176
+ Brand: NavigationBrand,
177
+ List: NavigationList,
178
+ Item: NavigationItem,
179
+ Action: NavigationAction,
180
+ });
181
+
182
+ /** @deprecated Use `Navigation` instead. */
183
+ export const AppNavigation = Navigation;
@@ -1,7 +1,11 @@
1
- export { AppNavigation } from "./app-navigation";
2
- export type { AppNavigationCta, AppNavigationLink, AppNavigationProps } from "./app-navigation";
1
+ export { AppNavigation, Navigation } from "./app-navigation";
2
+ export type {
3
+ NavigationActionProps,
4
+ NavigationBrandProps,
5
+ NavigationItemProps,
6
+ NavigationListProps,
7
+ NavigationRootProps,
8
+ } from "./app-navigation";
3
9
  export { PaginationControls } from "./pagination-controls";
4
10
  export type { PaginationControlsProps } from "./pagination-controls";
5
- export { RouteLink } from "./route-link";
6
- export type { RouteLinkProps } from "./route-link";
7
11
  export { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
@@ -35,7 +35,7 @@ export const Default: Story = {
35
35
  };
36
36
 
37
37
  export const SinglePage: Story = {
38
- name: "Single page (renders nothing)",
38
+ name: "Single page (disabled controls)",
39
39
  args: { ...baseArgs, totalPages: 1 },
40
40
  render: () => <PaginationPreview totalPages={1} />,
41
41
  };
@@ -8,41 +8,37 @@ export interface PaginationControlsProps {
8
8
  onPageChange: (page: number) => void;
9
9
  }
10
10
 
11
- /** Zero-indexed prev/next pagination control. Renders nothing for a single page. */
11
+ /** Zero-indexed prev/next pagination control. */
12
12
  export function PaginationControls({
13
13
  currentPage,
14
14
  totalPages,
15
15
  onPageChange,
16
16
  }: PaginationControlsProps) {
17
- if (totalPages <= 1) {
18
- return null;
19
- }
20
-
21
17
  return (
22
- <div className="text-muted-foreground flex items-center justify-end gap-2 text-sm">
18
+ <div className="border-border bg-background text-foreground inline-flex items-center overflow-hidden rounded-full border text-sm">
23
19
  <Button
24
20
  type="button"
25
21
  variant="outline"
26
- size="sm"
27
- className="border-dashed"
22
+ size="icon"
23
+ className="rounded-none border-0 border-r bg-transparent shadow-none"
28
24
  onClick={() => onPageChange(currentPage - 1)}
29
25
  disabled={currentPage === 0}
26
+ aria-label="Previous page"
30
27
  >
31
28
  <ChevronLeft className="size-4" aria-hidden />
32
- Previous
33
29
  </Button>
34
- <span className="text-foreground text-xs">
35
- Page {currentPage + 1} of {totalPages}
30
+ <span className="min-w-20 px-3 text-center text-xs tabular-nums">
31
+ {currentPage + 1} of {totalPages}
36
32
  </span>
37
33
  <Button
38
34
  type="button"
39
35
  variant="outline"
40
- size="sm"
41
- className="border-dashed"
36
+ size="icon"
37
+ className="rounded-none border-0 border-l bg-transparent shadow-none"
42
38
  onClick={() => onPageChange(currentPage + 1)}
43
39
  disabled={currentPage >= totalPages - 1}
40
+ aria-label="Next page"
44
41
  >
45
- Next
46
42
  <ChevronRight className="size-4" aria-hidden />
47
43
  </Button>
48
44
  </div>
@@ -44,9 +44,9 @@ export const Default: Story = {
44
44
  <Button variant="outline">Open Menu</Button>
45
45
  </DropdownMenuTrigger>
46
46
  <DropdownMenuContent className="w-56">
47
- <DropdownMenuLabel>My Account</DropdownMenuLabel>
48
- <DropdownMenuSeparator />
49
47
  <DropdownMenuGroup>
48
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
49
+ <DropdownMenuSeparator />
50
50
  <DropdownMenuItem>
51
51
  <User className="mr-2 size-4" />
52
52
  <span>Profile</span>
@@ -6,7 +6,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
6
  return (
7
7
  <div
8
8
  data-slot="card"
9
- className={cn("bg-card text-card-foreground flex flex-col rounded-xl border", className)}
9
+ className={cn("bg-card text-card-foreground flex flex-col gap-4 rounded border", className)}
10
10
  {...props}
11
11
  />
12
12
  );
@@ -17,7 +17,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
17
17
  <div
18
18
  data-slot="card-header"
19
19
  className={cn(
20
- "flex items-center justify-between gap-2 border-b px-4 py-3 has-data-[slot=card-action]:grid has-data-[slot=card-action]:grid-cols-[1fr_auto]",
20
+ "flex flex-col items-start justify-between gap-1 p-4 has-data-[slot=card-action]:grid has-data-[slot=card-action]:grid-cols-[1fr_auto]",
21
21
  className,
22
22
  )}
23
23
  {...props}
@@ -26,14 +26,14 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
26
26
  }
27
27
 
28
28
  function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
29
- return <div data-slot="card-title" className={cn(className)} {...props} />;
29
+ return <div data-slot="card-title" className={cn("text-xl", className)} {...props} />;
30
30
  }
31
31
 
32
32
  function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
33
33
  return (
34
34
  <div
35
35
  data-slot="card-description"
36
- className={cn("text-muted-foreground text-sm", className)}
36
+ className={cn("text-muted-foreground text-xs", className)}
37
37
  {...props}
38
38
  />
39
39
  );
@@ -55,12 +55,8 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
55
55
 
56
56
  function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
57
57
  return (
58
- <div
59
- data-slot="card-footer"
60
- className={cn("flex items-center border-t px-4 py-3", className)}
61
- {...props}
62
- />
58
+ <div data-slot="card-footer" className={cn("flex items-center p-4", className)} {...props} />
63
59
  );
64
60
  }
65
61
 
66
- export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
62
+ export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
@@ -27,21 +27,3 @@ export const WithInput: Story = {
27
27
  </div>
28
28
  ),
29
29
  };
30
-
31
- export const Disabled: Story = {
32
- render: () => (
33
- <div className="flex w-full max-w-sm flex-col gap-2">
34
- <Label htmlFor="disabled-field">Subscription plan</Label>
35
- <div className="flex items-center gap-3">
36
- <Input id="disabled-field" className="peer" disabled placeholder="Enterprise" />
37
- <Label htmlFor="disabled-field" className="shrink-0">
38
- (locked)
39
- </Label>
40
- </div>
41
- <p className="text-muted-foreground text-xs">
42
- The <code className="font-mono">peer-disabled</code> variant mutes the label when its paired
43
- input is disabled.
44
- </p>
45
- </div>
46
- ),
47
- };
@@ -1,20 +1,20 @@
1
- "use client";
2
-
3
- import { cva, type VariantProps } from "class-variance-authority";
4
1
  import * as React from "react";
5
2
 
6
3
  import { cn } from "../../lib/utils";
7
4
 
8
- const labelVariants = cva(
9
- "text-sm font-medium leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
5
+ const Label = React.forwardRef<HTMLLabelElement, React.LabelHTMLAttributes<HTMLLabelElement>>(
6
+ ({ className, ...props }, ref) => (
7
+ <label
8
+ htmlFor={props.htmlFor}
9
+ ref={ref}
10
+ className={cn(
11
+ "text-sm font-medium leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
12
+ className,
13
+ )}
14
+ {...props}
15
+ />
16
+ ),
10
17
  );
11
-
12
- const Label = React.forwardRef<
13
- HTMLLabelElement,
14
- React.LabelHTMLAttributes<HTMLLabelElement> & VariantProps<typeof labelVariants>
15
- >(({ className, ...props }, ref) => (
16
- <label ref={ref} className={cn(labelVariants(), className)} {...props} />
17
- ));
18
18
  Label.displayName = "Label";
19
19
 
20
20
  export { Label };
@@ -4,15 +4,15 @@
4
4
  */
5
5
 
6
6
  export const CHART_COLORS = {
7
- chart1: "var(--color-chart-1)",
8
- chart2: "var(--color-chart-2)",
9
- chart3: "var(--color-chart-3)",
10
- chart4: "var(--color-chart-4)",
11
- chart5: "var(--color-chart-5)",
7
+ chart1: "var(--chart-1)",
8
+ chart2: "var(--chart-2)",
9
+ chart3: "var(--chart-3)",
10
+ chart4: "var(--chart-4)",
11
+ chart5: "var(--chart-5)",
12
12
 
13
- positive: "var(--color-chart-positive)",
14
- negative: "var(--color-chart-negative)",
15
- neutral: "var(--color-chart-neutral)",
13
+ positive: "var(--chart-positive)",
14
+ negative: "var(--chart-negative)",
15
+ neutral: "var(--chart-neutral)",
16
16
 
17
17
  background: "var(--color-background)",
18
18
  grid: "var(--color-border)",
@@ -28,12 +28,12 @@ export const CHART_COLORS = {
28
28
  } as const;
29
29
 
30
30
  export const CHART_CSS_VARS = {
31
- positive: "var(--color-chart-positive)",
32
- negative: "var(--color-chart-negative)",
33
- neutral: "var(--color-chart-neutral)",
34
- chart1: "var(--color-chart-1)",
35
- chart2: "var(--color-chart-2)",
36
- chart3: "var(--color-chart-3)",
37
- chart4: "var(--color-chart-4)",
38
- chart5: "var(--color-chart-5)",
31
+ positive: "var(--chart-positive)",
32
+ negative: "var(--chart-negative)",
33
+ neutral: "var(--chart-neutral)",
34
+ chart1: "var(--chart-1)",
35
+ chart2: "var(--chart-2)",
36
+ chart3: "var(--chart-3)",
37
+ chart4: "var(--chart-4)",
38
+ chart5: "var(--chart-5)",
39
39
  } as const;
@@ -0,0 +1,29 @@
1
+ import { mergeProps } from "@base-ui/react/merge-props";
2
+ import type { ReactElement } from "react";
3
+ import * as React from "react";
4
+
5
+ export interface SlotProps extends React.HTMLAttributes<HTMLElement> {
6
+ children?: React.ReactNode;
7
+ }
8
+
9
+ /**
10
+ * Generic `asChild` primitive: merges the props it receives onto its single
11
+ * child instead of rendering its own DOM element. Lets compound components
12
+ * (e.g. `Navigation.Item`) apply base styling/aria wiring to whatever the
13
+ * app renders — a router `Link`, a plain `<a>`, anything — without the app
14
+ * passing a component as a prop; it's composed as JSX children instead.
15
+ *
16
+ * Uses `mergeProps` (already a dependency via `@base-ui/react`) so
17
+ * event handlers chain instead of one overwriting the other, and
18
+ * `className`/`style` merge instead of replacing.
19
+ */
20
+ export const Slot = React.forwardRef<HTMLElement, SlotProps>(({ children, ...props }, ref) => {
21
+ if (!React.isValidElement(children)) return null;
22
+ const child = children as ReactElement<Record<string, unknown>>;
23
+ const merged = mergeProps<"div">(
24
+ props as React.ComponentPropsWithRef<"div">,
25
+ child.props as React.ComponentPropsWithRef<"div">,
26
+ );
27
+ return React.cloneElement(child, { ...merged, ref } as Record<string, unknown>);
28
+ });
29
+ Slot.displayName = "Slot";
@@ -51,28 +51,6 @@
51
51
  }
52
52
  }
53
53
 
54
- .animate-in {
55
- animation:
56
- fade-in 150ms ease-out,
57
- zoom-in 150ms ease-out;
58
- }
59
- .animate-out {
60
- animation:
61
- fade-out 120ms ease-in,
62
- zoom-out 120ms ease-in;
63
- }
64
- .fade-in-0 {
65
- animation-name: fade-in;
66
- }
67
- .fade-out-0 {
68
- animation-name: fade-out;
69
- }
70
- .zoom-in-95 {
71
- animation-name: zoom-in;
72
- }
73
- .zoom-out-95 {
74
- animation-name: zoom-out;
75
- }
76
54
  .data-open\:animate-in[data-open],
77
55
  .data-open\:animate-in[data-state="open"] {
78
56
  animation: