@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.
- package/README.md +13 -0
- package/package.json +22 -20
- package/src/components/data-display/accordion.tsx +1 -1
- package/src/components/data-display/sort-controls.tsx +3 -3
- package/src/components/feedback/index.ts +0 -2
- package/src/components/forms/date-month-select.tsx +12 -3
- package/src/components/forms/date-picker.tsx +16 -12
- package/src/components/forms/drop-zone.tsx +1 -1
- package/src/components/forms/index.ts +4 -8
- package/src/components/forms/passkey-management.tsx +4 -10
- package/src/components/forms/radio-group.tsx +8 -4
- package/src/components/forms/select.tsx +1 -1
- package/src/components/forms/text-field.tsx +40 -5
- package/src/components/navigation/app-navigation.stories.tsx +111 -21
- package/src/components/navigation/app-navigation.tsx +165 -137
- package/src/components/navigation/index.ts +8 -4
- package/src/components/navigation/pagination-controls.stories.tsx +1 -1
- package/src/components/navigation/pagination-controls.tsx +10 -14
- package/src/components/overlays/dropdown-menu.stories.tsx +2 -2
- package/src/components/primitives/card.tsx +6 -10
- package/src/components/primitives/label.stories.tsx +0 -18
- package/src/components/primitives/label.tsx +12 -12
- package/src/constants/chart-colors.ts +16 -16
- package/src/lib/slot.tsx +29 -0
- package/src/styles/animations.css +0 -22
- package/src/styles/index.css +60 -6
- package/src/styles/tokens/color-systems.stories.tsx +2 -1
- package/src/styles/tokens/generated/light.colors.generated.ts +3 -3
- package/src/styles/tokens/generated/light.css +3 -3
- package/src/styles/tokens/source/light.tokens.json +3 -3
- package/src/styles/tokens/style-dictionary.light-storybook.json +16 -0
- package/src/components/feedback/file-upload-status-badge.stories.tsx +0 -28
- package/src/components/feedback/file-upload-status-badge.tsx +0 -33
- package/src/components/forms/field.stories.tsx +0 -62
- package/src/components/forms/field.tsx +0 -65
- package/src/components/forms/field.types.ts +0 -7
- package/src/components/forms/group-by-select.stories.tsx +0 -26
- package/src/components/forms/group-by-select.tsx +0 -47
- package/src/components/navigation/route-link.stories.tsx +0 -30
- package/src/components/navigation/route-link.tsx +0 -42
- package/src/hooks/use-derived-filter-state.stories.tsx +0 -136
- 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 {
|
|
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
|
-
|
|
8
|
-
|
|
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
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
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-
|
|
87
|
-
<nav
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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 {
|
|
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 (
|
|
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.
|
|
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-
|
|
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="
|
|
27
|
-
className="border-
|
|
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-
|
|
35
|
-
|
|
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="
|
|
41
|
-
className="border-
|
|
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
|
|
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-
|
|
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-
|
|
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,
|
|
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
|
|
9
|
-
|
|
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(--
|
|
8
|
-
chart2: "var(--
|
|
9
|
-
chart3: "var(--
|
|
10
|
-
chart4: "var(--
|
|
11
|
-
chart5: "var(--
|
|
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(--
|
|
14
|
-
negative: "var(--
|
|
15
|
-
neutral: "var(--
|
|
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(--
|
|
32
|
-
negative: "var(--
|
|
33
|
-
neutral: "var(--
|
|
34
|
-
chart1: "var(--
|
|
35
|
-
chart2: "var(--
|
|
36
|
-
chart3: "var(--
|
|
37
|
-
chart4: "var(--
|
|
38
|
-
chart5: "var(--
|
|
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;
|
package/src/lib/slot.tsx
ADDED
|
@@ -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:
|