@kinetixui/ui 0.10.0 → 0.11.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/dist/index.d.ts CHANGED
@@ -1372,7 +1372,66 @@ interface TimelineProps extends React$1.HTMLAttributes<HTMLOListElement> {
1372
1372
  }
1373
1373
  declare const Timeline: React$1.ForwardRefExoticComponent<TimelineProps & React$1.RefAttributes<HTMLOListElement>>;
1374
1374
 
1375
+ /**
1376
+ * Marquee — an auto-scrolling horizontal ticker (logo strip, testimonials),
1377
+ * pausing on hover and respecting `prefers-reduced-motion`. Lifted out of
1378
+ * the marketing site's `reveal.tsx` (`.kx-marquee`/`.kx-marquee-track` in
1379
+ * its `globals.css`) into a portable component: the site version leaned on
1380
+ * a hand-written CSS file a CLI-installed app wouldn't have, so this port
1381
+ * moves the keyframe onto the shared Tailwind preset (`animate-marquee`,
1382
+ * `motion-reduce:animate-none`) instead. Also fixes an accessibility gap
1383
+ * in the site version, which marked *both* content copies `aria-hidden`
1384
+ * (hiding the marquee from screen readers entirely) — only the duplicate
1385
+ * copy needed for the seamless loop is hidden here; the first copy stays
1386
+ * in the accessible DOM flow. Gap-fill addition (not in the original
1387
+ * Figma source).
1388
+ */
1389
+ interface MarqueeProps extends React$1.HTMLAttributes<HTMLDivElement> {
1390
+ /** seconds for one full loop */
1391
+ durationSeconds?: number;
1392
+ pauseOnHover?: boolean;
1393
+ }
1394
+ declare const Marquee: React$1.ForwardRefExoticComponent<MarqueeProps & React$1.RefAttributes<HTMLDivElement>>;
1395
+
1396
+ /**
1397
+ * ComparisonSlider — a drag handle wiping between two stacked layers
1398
+ * (before/after image, redesign preview, …). Built on Radix's `Slider`
1399
+ * for the drag + keyboard + ARIA behavior (a plain 0–100 value), but
1400
+ * fully custom-drawn: Radix's own `Range` fill can't be reused as the
1401
+ * divider line (it's Radix's own inline `width` style, which would win
1402
+ * over any Tailwind width class), so the line is a separate element
1403
+ * positioned from the same value instead. Gap-fill addition (not in the
1404
+ * original Figma source).
1405
+ */
1406
+ interface ComparisonSliderProps extends Omit<React$1.HTMLAttributes<HTMLDivElement>, "onChange"> {
1407
+ before: React$1.ReactNode;
1408
+ after: React$1.ReactNode;
1409
+ beforeLabel?: React$1.ReactNode;
1410
+ afterLabel?: React$1.ReactNode;
1411
+ value?: number;
1412
+ defaultValue?: number;
1413
+ onValueChange?: (value: number) => void;
1414
+ }
1415
+ declare const ComparisonSlider: React$1.ForwardRefExoticComponent<ComparisonSliderProps & React$1.RefAttributes<HTMLDivElement>>;
1416
+
1417
+ /**
1418
+ * PageHeader — title + optional breadcrumb + description + action
1419
+ * cluster + optional tabs row. A recipe every docs/app screen re-lays
1420
+ * out by hand; `breadcrumb`/`actions`/`tabs` are plain slots so callers
1421
+ * compose their own `Breadcrumb`/`Button`/`Tabs` (or `SegmentedControl`)
1422
+ * into them rather than PageHeader re-implementing any of those. Gap-fill
1423
+ * addition (not in the original Figma source).
1424
+ */
1425
+ interface PageHeaderProps extends Omit<React$1.HTMLAttributes<HTMLDivElement>, "title"> {
1426
+ title: React$1.ReactNode;
1427
+ description?: React$1.ReactNode;
1428
+ breadcrumb?: React$1.ReactNode;
1429
+ actions?: React$1.ReactNode;
1430
+ tabs?: React$1.ReactNode;
1431
+ }
1432
+ declare const PageHeader: React$1.ForwardRefExoticComponent<PageHeaderProps & React$1.RefAttributes<HTMLDivElement>>;
1433
+
1375
1434
  /** Merges Tailwind class names, resolving conflicts (clsx + tailwind-merge). */
1376
1435
  declare function cn(...inputs: ClassValue[]): string;
1377
1436
 
1378
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppBar, AppBarActions, AppBarBrand, AppBarLink, type AppBarLinkProps, AppBarNav, AspectRatio, AudioPlayer, type AudioPlayerProps, Avatar, AvatarFallback, AvatarGroup, type AvatarGroupProps, AvatarImage, Badge, type BadgeProps, Banner, type BannerProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, ButtonGroup, type ButtonGroupProps, ButtonGroupSeparator, type ButtonGroupSeparatorProps, ButtonGroupText, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, CircularProgress, type CircularProgressProps, CodeBlock, type CodeBlockFile, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DataTable, DatePicker, type DatePickerProps, DescriptionList, DescriptionListItem, type DescriptionListItemProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, type EmptyMediaProps, EmptyTitle, Fab, type FabProps, Field, FieldControl, FieldDescription, FieldLabel, FieldMessage, FileUpload, FileUploadItem, type FileUploadItemProps, type FileUploadProps, Footer, FooterBottom, FooterColumn, type FooterColumnProps, FooterLink, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, HoverCard, HoverCardContent, HoverCardTrigger, Image, type ImageProps, Inform, type InformProps, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Kbd, KbdGroup, type KbdGroupProps, type KbdProps, Label, List, ListItem, type ListItemProps, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, Metric, type MetricProps, Modal, type ModalProps, type ModalType, NativeSelect, NativeSelectOptGroup, type NativeSelectOptGroupProps, NativeSelectOption, type NativeSelectOptionProps, type NativeSelectProps, NavigationBar, type NavigationBarProps, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, NumberInput, type NumberInputProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PasswordInput, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, Quote, type QuoteProps, RadioGroup, RadioGroupItem, Rating, type RatingProps, ResizableHandle, ResizablePanel, ResizablePanelGroup, ScrollArea, ScrollBar, SegmentedControl, SegmentedControlItem, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarProvider, SidebarSeparator, SidebarTrigger, Skeleton, Slider, Spinner, type SpinnerProps, Stepper, type StepperProps, type StepperStep, Switch, TabBar, TabBarItem, type TabBarItemProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableOfContents, type TableOfContentsProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, Textarea, type TextareaProps, Timeline, type TimelineItem, type TimelineProps, Toaster, type TocItem, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, badgeVariants, bannerVariants, buttonVariants, cn, emptyMediaVariants, fabVariants, formatBytes, informVariants, inputVariants, messageVariants, nativeSelectVariants, navigationMenuTriggerStyle, spinnerVariants, tagVariants, textareaVariants, toggleVariants, useAppBar, useFormField, useSidebar };
1437
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppBar, AppBarActions, AppBarBrand, AppBarLink, type AppBarLinkProps, AppBarNav, AspectRatio, AudioPlayer, type AudioPlayerProps, Avatar, AvatarFallback, AvatarGroup, type AvatarGroupProps, AvatarImage, Badge, type BadgeProps, Banner, type BannerProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, ButtonGroup, type ButtonGroupProps, ButtonGroupSeparator, type ButtonGroupSeparatorProps, ButtonGroupText, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent, Checkbox, CircularProgress, type CircularProgressProps, CodeBlock, type CodeBlockFile, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ComparisonSlider, type ComparisonSliderProps, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, DataTable, DatePicker, type DatePickerProps, DescriptionList, DescriptionListItem, type DescriptionListItemProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, type EmptyMediaProps, EmptyTitle, Fab, type FabProps, Field, FieldControl, FieldDescription, FieldLabel, FieldMessage, FileUpload, FileUploadItem, type FileUploadItemProps, type FileUploadProps, Footer, FooterBottom, FooterColumn, type FooterColumnProps, FooterLink, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, HoverCard, HoverCardContent, HoverCardTrigger, Image, type ImageProps, Inform, type InformProps, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Kbd, KbdGroup, type KbdGroupProps, type KbdProps, Label, List, ListItem, type ListItemProps, Marquee, type MarqueeProps, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, Metric, type MetricProps, Modal, type ModalProps, type ModalType, NativeSelect, NativeSelectOptGroup, type NativeSelectOptGroupProps, NativeSelectOption, type NativeSelectOptionProps, type NativeSelectProps, NavigationBar, type NavigationBarProps, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, NumberInput, type NumberInputProps, PageHeader, type PageHeaderProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PasswordInput, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, Quote, type QuoteProps, RadioGroup, RadioGroupItem, Rating, type RatingProps, ResizableHandle, ResizablePanel, ResizablePanelGroup, ScrollArea, ScrollBar, SegmentedControl, SegmentedControlItem, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarProvider, SidebarSeparator, SidebarTrigger, Skeleton, Slider, Spinner, type SpinnerProps, Stepper, type StepperProps, type StepperStep, Switch, TabBar, TabBarItem, type TabBarItemProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableOfContents, type TableOfContentsProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, Textarea, type TextareaProps, Timeline, type TimelineItem, type TimelineProps, Toaster, type TocItem, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, badgeVariants, bannerVariants, buttonVariants, cn, emptyMediaVariants, fabVariants, formatBytes, informVariants, inputVariants, messageVariants, nativeSelectVariants, navigationMenuTriggerStyle, spinnerVariants, tagVariants, textareaVariants, toggleVariants, useAppBar, useFormField, useSidebar };
package/dist/index.js CHANGED
@@ -4206,6 +4206,130 @@ var Timeline = React73.forwardRef(
4206
4206
  }) })
4207
4207
  );
4208
4208
  Timeline.displayName = "Timeline";
4209
+
4210
+ // src/components/marquee.tsx
4211
+ import * as React74 from "react";
4212
+ import { jsx as jsx79, jsxs as jsxs45 } from "react/jsx-runtime";
4213
+ var Marquee = React74.forwardRef(
4214
+ ({ className, children, durationSeconds = 32, pauseOnHover = true, style, ...props }, ref) => /* @__PURE__ */ jsx79(
4215
+ "div",
4216
+ {
4217
+ ref,
4218
+ className: cn(
4219
+ "group relative flex overflow-hidden",
4220
+ "[mask-image:linear-gradient(90deg,transparent,black_8%,black_92%,transparent)]",
4221
+ className
4222
+ ),
4223
+ style: { ["--marquee-duration"]: `${durationSeconds}s`, ...style },
4224
+ ...props,
4225
+ children: /* @__PURE__ */ jsxs45(
4226
+ "div",
4227
+ {
4228
+ className: cn(
4229
+ "flex w-max animate-marquee motion-reduce:animate-none",
4230
+ pauseOnHover && "group-hover:[animation-play-state:paused]"
4231
+ ),
4232
+ children: [
4233
+ /* @__PURE__ */ jsx79("div", { className: "flex shrink-0 items-center", children }),
4234
+ /* @__PURE__ */ jsx79("div", { "aria-hidden": "true", className: "flex shrink-0 items-center", children })
4235
+ ]
4236
+ }
4237
+ )
4238
+ }
4239
+ )
4240
+ );
4241
+ Marquee.displayName = "Marquee";
4242
+
4243
+ // src/components/comparison-slider.tsx
4244
+ import * as React75 from "react";
4245
+ import * as SliderPrimitive2 from "@radix-ui/react-slider";
4246
+ import { GripVertical as GripVertical2 } from "lucide-react";
4247
+ import { jsx as jsx80, jsxs as jsxs46 } from "react/jsx-runtime";
4248
+ var ComparisonSlider = React75.forwardRef(
4249
+ ({ className, before, after, beforeLabel, afterLabel, value, defaultValue = 50, onValueChange, ...props }, ref) => {
4250
+ const [internal, setInternal] = React75.useState(defaultValue);
4251
+ const current = value ?? internal;
4252
+ const handleChange = (vals) => {
4253
+ const v = vals[0] ?? current;
4254
+ setInternal(v);
4255
+ onValueChange?.(v);
4256
+ };
4257
+ return /* @__PURE__ */ jsxs46(
4258
+ "div",
4259
+ {
4260
+ ref,
4261
+ className: cn(
4262
+ "relative aspect-video w-full select-none overflow-hidden rounded-md bg-muted font-sans",
4263
+ "[&_img]:size-full [&_img]:object-cover [&_video]:size-full [&_video]:object-cover",
4264
+ className
4265
+ ),
4266
+ ...props,
4267
+ children: [
4268
+ /* @__PURE__ */ jsx80("div", { className: "absolute inset-0", children: before }),
4269
+ /* @__PURE__ */ jsx80("div", { className: "absolute inset-0 overflow-hidden", style: { clipPath: `inset(0 0 0 ${current}%)` }, children: after }),
4270
+ /* @__PURE__ */ jsx80(
4271
+ "div",
4272
+ {
4273
+ "aria-hidden": true,
4274
+ className: "absolute inset-y-0 w-0.5 -translate-x-1/2 bg-background shadow-sm",
4275
+ style: { left: `${current}%` }
4276
+ }
4277
+ ),
4278
+ beforeLabel && /* @__PURE__ */ jsx80("span", { className: "absolute left-2 top-2 rounded bg-background/80 px-2 py-0.5 text-label-sm text-foreground backdrop-blur-sm", children: beforeLabel }),
4279
+ afterLabel && /* @__PURE__ */ jsx80("span", { className: "absolute right-2 top-2 rounded bg-background/80 px-2 py-0.5 text-label-sm text-foreground backdrop-blur-sm", children: afterLabel }),
4280
+ /* @__PURE__ */ jsxs46(
4281
+ SliderPrimitive2.Root,
4282
+ {
4283
+ value: [current],
4284
+ onValueChange: handleChange,
4285
+ max: 100,
4286
+ step: 0.1,
4287
+ className: "absolute inset-0 flex touch-none items-center",
4288
+ children: [
4289
+ /* @__PURE__ */ jsx80(SliderPrimitive2.Track, { className: "relative h-full w-full grow bg-transparent", children: /* @__PURE__ */ jsx80(SliderPrimitive2.Range, { className: "absolute h-full bg-transparent" }) }),
4290
+ /* @__PURE__ */ jsx80(
4291
+ SliderPrimitive2.Thumb,
4292
+ {
4293
+ "aria-label": "Comparison position",
4294
+ className: "flex size-8 shrink-0 items-center justify-center rounded-full border-2 border-background bg-background/90 text-foreground shadow-md outline-none focus-visible:ring-2 focus-visible:ring-ring",
4295
+ children: /* @__PURE__ */ jsx80(GripVertical2, { className: "size-4" })
4296
+ }
4297
+ )
4298
+ ]
4299
+ }
4300
+ )
4301
+ ]
4302
+ }
4303
+ );
4304
+ }
4305
+ );
4306
+ ComparisonSlider.displayName = "ComparisonSlider";
4307
+
4308
+ // src/components/page-header.tsx
4309
+ import * as React76 from "react";
4310
+ import { jsx as jsx81, jsxs as jsxs47 } from "react/jsx-runtime";
4311
+ var PageHeader = React76.forwardRef(
4312
+ ({ className, title, description, breadcrumb, actions, tabs, ...props }, ref) => /* @__PURE__ */ jsxs47(
4313
+ "div",
4314
+ {
4315
+ ref,
4316
+ className: cn("flex flex-col gap-4 border-b border-border pb-6 font-sans", className),
4317
+ ...props,
4318
+ children: [
4319
+ breadcrumb && /* @__PURE__ */ jsx81("div", { className: "text-body-sm", children: breadcrumb }),
4320
+ /* @__PURE__ */ jsxs47("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [
4321
+ /* @__PURE__ */ jsxs47("div", { className: "flex min-w-0 flex-col gap-1", children: [
4322
+ /* @__PURE__ */ jsx81("h1", { className: "text-headline-sm font-medium text-foreground", children: title }),
4323
+ description && /* @__PURE__ */ jsx81("p", { className: "text-body-md text-muted-foreground", children: description })
4324
+ ] }),
4325
+ actions && /* @__PURE__ */ jsx81("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: actions })
4326
+ ] }),
4327
+ tabs && /* @__PURE__ */ jsx81("div", { className: "-mb-6", children: tabs })
4328
+ ]
4329
+ }
4330
+ )
4331
+ );
4332
+ PageHeader.displayName = "PageHeader";
4209
4333
  export {
4210
4334
  Accordion,
4211
4335
  AccordionContent,
@@ -4282,6 +4406,7 @@ export {
4282
4406
  CommandList,
4283
4407
  CommandSeparator,
4284
4408
  CommandShortcut,
4409
+ ComparisonSlider,
4285
4410
  ContextMenu,
4286
4411
  ContextMenuCheckboxItem,
4287
4412
  ContextMenuContent,
@@ -4381,6 +4506,7 @@ export {
4381
4506
  Label2 as Label,
4382
4507
  List3 as List,
4383
4508
  ListItem,
4509
+ Marquee,
4384
4510
  Menubar,
4385
4511
  MenubarCheckboxItem,
4386
4512
  MenubarContent,
@@ -4412,6 +4538,7 @@ export {
4412
4538
  NavigationMenuTrigger,
4413
4539
  NavigationMenuViewport,
4414
4540
  NumberInput,
4541
+ PageHeader,
4415
4542
  Pagination,
4416
4543
  PaginationContent,
4417
4544
  PaginationEllipsis,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kinetixui/ui",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "private": false,
5
5
  "description": "KinetixUI React components — CVA + Radix + Tailwind on the KinetixUI token contract. Also distributed via the kinetixui CLI as a component registry.",
6
6
  "keywords": [
@@ -90,7 +90,7 @@
90
90
  "react-dom": ">=18"
91
91
  },
92
92
  "devDependencies": {
93
- "@kinetixui/tokens": "0.10.0",
93
+ "@kinetixui/tokens": "0.11.0",
94
94
  "@testing-library/jest-dom": "^7.0.1",
95
95
  "@testing-library/react": "^16.3.3",
96
96
  "@testing-library/user-event": "^14.6.7",
@@ -0,0 +1,98 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SliderPrimitive from "@radix-ui/react-slider";
5
+ import { GripVertical } from "lucide-react";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * ComparisonSlider — a drag handle wiping between two stacked layers
10
+ * (before/after image, redesign preview, …). Built on Radix's `Slider`
11
+ * for the drag + keyboard + ARIA behavior (a plain 0–100 value), but
12
+ * fully custom-drawn: Radix's own `Range` fill can't be reused as the
13
+ * divider line (it's Radix's own inline `width` style, which would win
14
+ * over any Tailwind width class), so the line is a separate element
15
+ * positioned from the same value instead. Gap-fill addition (not in the
16
+ * original Figma source).
17
+ */
18
+ export interface ComparisonSliderProps
19
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
20
+ before: React.ReactNode;
21
+ after: React.ReactNode;
22
+ beforeLabel?: React.ReactNode;
23
+ afterLabel?: React.ReactNode;
24
+ value?: number;
25
+ defaultValue?: number;
26
+ onValueChange?: (value: number) => void;
27
+ }
28
+
29
+ const ComparisonSlider = React.forwardRef<HTMLDivElement, ComparisonSliderProps>(
30
+ (
31
+ { className, before, after, beforeLabel, afterLabel, value, defaultValue = 50, onValueChange, ...props },
32
+ ref,
33
+ ) => {
34
+ const [internal, setInternal] = React.useState(defaultValue);
35
+ const current = value ?? internal;
36
+
37
+ const handleChange = (vals: number[]) => {
38
+ const v = vals[0] ?? current;
39
+ setInternal(v);
40
+ onValueChange?.(v);
41
+ };
42
+
43
+ return (
44
+ <div
45
+ ref={ref}
46
+ className={cn(
47
+ "relative aspect-video w-full select-none overflow-hidden rounded-md bg-muted font-sans",
48
+ "[&_img]:size-full [&_img]:object-cover [&_video]:size-full [&_video]:object-cover",
49
+ className,
50
+ )}
51
+ {...props}
52
+ >
53
+ <div className="absolute inset-0">{before}</div>
54
+ <div className="absolute inset-0 overflow-hidden" style={{ clipPath: `inset(0 0 0 ${current}%)` }}>
55
+ {after}
56
+ </div>
57
+
58
+ <div
59
+ aria-hidden
60
+ className="absolute inset-y-0 w-0.5 -translate-x-1/2 bg-background shadow-sm"
61
+ style={{ left: `${current}%` }}
62
+ />
63
+
64
+ {beforeLabel && (
65
+ <span className="absolute left-2 top-2 rounded bg-background/80 px-2 py-0.5 text-label-sm text-foreground backdrop-blur-sm">
66
+ {beforeLabel}
67
+ </span>
68
+ )}
69
+ {afterLabel && (
70
+ <span className="absolute right-2 top-2 rounded bg-background/80 px-2 py-0.5 text-label-sm text-foreground backdrop-blur-sm">
71
+ {afterLabel}
72
+ </span>
73
+ )}
74
+
75
+ <SliderPrimitive.Root
76
+ value={[current]}
77
+ onValueChange={handleChange}
78
+ max={100}
79
+ step={0.1}
80
+ className="absolute inset-0 flex touch-none items-center"
81
+ >
82
+ <SliderPrimitive.Track className="relative h-full w-full grow bg-transparent">
83
+ <SliderPrimitive.Range className="absolute h-full bg-transparent" />
84
+ </SliderPrimitive.Track>
85
+ <SliderPrimitive.Thumb
86
+ aria-label="Comparison position"
87
+ className="flex size-8 shrink-0 items-center justify-center rounded-full border-2 border-background bg-background/90 text-foreground shadow-md outline-none focus-visible:ring-2 focus-visible:ring-ring"
88
+ >
89
+ <GripVertical className="size-4" />
90
+ </SliderPrimitive.Thumb>
91
+ </SliderPrimitive.Root>
92
+ </div>
93
+ );
94
+ },
95
+ );
96
+ ComparisonSlider.displayName = "ComparisonSlider";
97
+
98
+ export { ComparisonSlider };
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "../lib/utils";
5
+
6
+ /**
7
+ * Marquee — an auto-scrolling horizontal ticker (logo strip, testimonials),
8
+ * pausing on hover and respecting `prefers-reduced-motion`. Lifted out of
9
+ * the marketing site's `reveal.tsx` (`.kx-marquee`/`.kx-marquee-track` in
10
+ * its `globals.css`) into a portable component: the site version leaned on
11
+ * a hand-written CSS file a CLI-installed app wouldn't have, so this port
12
+ * moves the keyframe onto the shared Tailwind preset (`animate-marquee`,
13
+ * `motion-reduce:animate-none`) instead. Also fixes an accessibility gap
14
+ * in the site version, which marked *both* content copies `aria-hidden`
15
+ * (hiding the marquee from screen readers entirely) — only the duplicate
16
+ * copy needed for the seamless loop is hidden here; the first copy stays
17
+ * in the accessible DOM flow. Gap-fill addition (not in the original
18
+ * Figma source).
19
+ */
20
+ export interface MarqueeProps extends React.HTMLAttributes<HTMLDivElement> {
21
+ /** seconds for one full loop */
22
+ durationSeconds?: number;
23
+ pauseOnHover?: boolean;
24
+ }
25
+
26
+ const Marquee = React.forwardRef<HTMLDivElement, MarqueeProps>(
27
+ ({ className, children, durationSeconds = 32, pauseOnHover = true, style, ...props }, ref) => (
28
+ <div
29
+ ref={ref}
30
+ className={cn(
31
+ "group relative flex overflow-hidden",
32
+ "[mask-image:linear-gradient(90deg,transparent,black_8%,black_92%,transparent)]",
33
+ className,
34
+ )}
35
+ style={{ ["--marquee-duration" as string]: `${durationSeconds}s`, ...style }}
36
+ {...props}
37
+ >
38
+ {/* the track is 2x content width (two copies side by side); animating it
39
+ exactly -50% of its own width moves it one content-width, which is
40
+ what makes the loop seamless when it snaps back to 0. */}
41
+ <div
42
+ className={cn(
43
+ "flex w-max animate-marquee motion-reduce:animate-none",
44
+ pauseOnHover && "group-hover:[animation-play-state:paused]",
45
+ )}
46
+ >
47
+ <div className="flex shrink-0 items-center">{children}</div>
48
+ <div aria-hidden="true" className="flex shrink-0 items-center">
49
+ {children}
50
+ </div>
51
+ </div>
52
+ </div>
53
+ ),
54
+ );
55
+ Marquee.displayName = "Marquee";
56
+
57
+ export { Marquee };
@@ -0,0 +1,43 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "../lib/utils";
5
+
6
+ /**
7
+ * PageHeader — title + optional breadcrumb + description + action
8
+ * cluster + optional tabs row. A recipe every docs/app screen re-lays
9
+ * out by hand; `breadcrumb`/`actions`/`tabs` are plain slots so callers
10
+ * compose their own `Breadcrumb`/`Button`/`Tabs` (or `SegmentedControl`)
11
+ * into them rather than PageHeader re-implementing any of those. Gap-fill
12
+ * addition (not in the original Figma source).
13
+ */
14
+ export interface PageHeaderProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
15
+ title: React.ReactNode;
16
+ description?: React.ReactNode;
17
+ breadcrumb?: React.ReactNode;
18
+ actions?: React.ReactNode;
19
+ tabs?: React.ReactNode;
20
+ }
21
+
22
+ const PageHeader = React.forwardRef<HTMLDivElement, PageHeaderProps>(
23
+ ({ className, title, description, breadcrumb, actions, tabs, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col gap-4 border-b border-border pb-6 font-sans", className)}
27
+ {...props}
28
+ >
29
+ {breadcrumb && <div className="text-body-sm">{breadcrumb}</div>}
30
+ <div className="flex flex-wrap items-start justify-between gap-4">
31
+ <div className="flex min-w-0 flex-col gap-1">
32
+ <h1 className="text-headline-sm font-medium text-foreground">{title}</h1>
33
+ {description && <p className="text-body-md text-muted-foreground">{description}</p>}
34
+ </div>
35
+ {actions && <div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div>}
36
+ </div>
37
+ {tabs && <div className="-mb-6">{tabs}</div>}
38
+ </div>
39
+ ),
40
+ );
41
+ PageHeader.displayName = "PageHeader";
42
+
43
+ export { PageHeader };
package/src/index.ts CHANGED
@@ -351,5 +351,8 @@ export {
351
351
  type SegmentedControlProps,
352
352
  } from "./components/segmented-control";
353
353
  export { Timeline, type TimelineItem, type TimelineProps } from "./components/timeline";
354
+ export { Marquee, type MarqueeProps } from "./components/marquee";
355
+ export { ComparisonSlider, type ComparisonSliderProps } from "./components/comparison-slider";
356
+ export { PageHeader, type PageHeaderProps } from "./components/page-header";
354
357
 
355
358
  export { cn } from "./lib/utils";
@@ -0,0 +1,27 @@
1
+ /* AUTO-GENERATED by scripts/gen-stories.mjs — do not edit.
2
+ Source of truth: apps/web/src/registry/demos.tsx */
3
+ import * as React from "react";
4
+ import type { Meta, StoryObj } from "@storybook/react-vite";
5
+ import { ComparisonSlider } from "@kinetixui/ui";
6
+
7
+ const Demo = () => (
8
+ <ComparisonSlider
9
+ className="w-full max-w-md"
10
+ beforeLabel="Before"
11
+ afterLabel="After"
12
+ before={<div className="flex size-full items-center justify-center bg-muted text-body-sm text-muted-foreground">Original</div>}
13
+ after={<div className="flex size-full items-center justify-center bg-primary/15 text-body-sm text-primary">Redesign</div>}
14
+ />
15
+ );
16
+
17
+ const meta = {
18
+ title: "Controls & Actions/ComparisonSlider",
19
+ parameters: {
20
+ layout: "centered",
21
+ docs: { source: { code: "<ComparisonSlider\\n beforeLabel=\"Before\"\\n afterLabel=\"After\"\\n before={<img src=\"/before.jpg\" alt=\"\" />}\\n after={<img src=\"/after.jpg\" alt=\"\" />}\\n/>", language: "tsx" } },
22
+ },
23
+ } satisfies Meta;
24
+
25
+ export default meta;
26
+
27
+ export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -0,0 +1,28 @@
1
+ /* AUTO-GENERATED by scripts/gen-stories.mjs — do not edit.
2
+ Source of truth: apps/web/src/registry/demos.tsx */
3
+ import * as React from "react";
4
+ import type { Meta, StoryObj } from "@storybook/react-vite";
5
+ import { Marquee } from "@kinetixui/ui";
6
+
7
+ const Demo = () => (
8
+ <Marquee durationSeconds={18} className="w-full max-w-md">
9
+ {["React", "SwiftUI", "Jetpack Compose", "Flutter"].map((p) => (
10
+ <span key={p} className="flex items-center gap-2 px-4 text-body-sm text-muted-foreground">
11
+ <span className="size-1.5 rounded-full bg-primary" />
12
+ {p}
13
+ </span>
14
+ ))}
15
+ </Marquee>
16
+ );
17
+
18
+ const meta = {
19
+ title: "Data Display/Marquee",
20
+ parameters: {
21
+ layout: "centered",
22
+ docs: { source: { code: "<Marquee durationSeconds={18}>\\n {items.map((item) => (\\n <span key={item}>{item}</span>\\n ))}\\n</Marquee>", language: "tsx" } },
23
+ },
24
+ } satisfies Meta;
25
+
26
+ export default meta;
27
+
28
+ export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -0,0 +1,47 @@
1
+ /* AUTO-GENERATED by scripts/gen-stories.mjs — do not edit.
2
+ Source of truth: apps/web/src/registry/demos.tsx */
3
+ import * as React from "react";
4
+ import type { Meta, StoryObj } from "@storybook/react-vite";
5
+ import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, PageHeader } from "@kinetixui/ui";
6
+ import { Settings } from "lucide-react";
7
+
8
+ const Demo = () => (
9
+ <PageHeader
10
+ className="w-full"
11
+ breadcrumb={
12
+ <Breadcrumb>
13
+ <BreadcrumbList>
14
+ <BreadcrumbItem>
15
+ <BreadcrumbLink href="#">Projects</BreadcrumbLink>
16
+ </BreadcrumbItem>
17
+ <BreadcrumbSeparator />
18
+ <BreadcrumbItem>
19
+ <BreadcrumbPage>KinetixUI</BreadcrumbPage>
20
+ </BreadcrumbItem>
21
+ </BreadcrumbList>
22
+ </Breadcrumb>
23
+ }
24
+ title="KinetixUI"
25
+ description="One token architecture, in motion across every platform."
26
+ actions={
27
+ <>
28
+ <Button variant="Outline" size="sm">
29
+ Settings
30
+ </Button>
31
+ <Button size="sm">Deploy</Button>
32
+ </>
33
+ }
34
+ />
35
+ );
36
+
37
+ const meta = {
38
+ title: "Navigation/PageHeader",
39
+ parameters: {
40
+ layout: "centered",
41
+ docs: { source: { code: "<PageHeader\\n breadcrumb={<Breadcrumb>...</Breadcrumb>}\\n title=\"KinetixUI\"\\n description=\"One token architecture, in motion across every platform.\"\\n actions={<><Button variant=\"Outline\" size=\"sm\">Settings</Button><Button size=\"sm\">Deploy</Button></>}\\n/>", language: "tsx" } },
42
+ },
43
+ } satisfies Meta;
44
+
45
+ export default meta;
46
+
47
+ export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -127,11 +127,13 @@ export default {
127
127
  "accordion-down": { from: { height: "0" }, to: { height: "var(--radix-accordion-content-height)" } },
128
128
  "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" } },
129
129
  "caret-blink": { "0%,70%,100%": { opacity: "1" }, "20%,50%": { opacity: "0" } },
130
+ marquee: { from: { transform: "translateX(0)" }, to: { transform: "translateX(-50%)" } },
130
131
  },
131
132
  animation: {
132
133
  "accordion-down": "accordion-down 0.2s ease-out",
133
134
  "accordion-up": "accordion-up 0.2s ease-out",
134
135
  "caret-blink": "caret-blink 1.25s ease-out infinite",
136
+ marquee: "marquee var(--marquee-duration, 32s) linear infinite",
135
137
  },
136
138
  },
137
139
  },