@kinetixui/ui 0.9.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 +137 -1
- package/dist/index.js +297 -0
- package/package.json +2 -2
- package/src/components/banner.tsx +89 -0
- package/src/components/comparison-slider.tsx +98 -0
- package/src/components/description-list.tsx +52 -0
- package/src/components/marquee.tsx +57 -0
- package/src/components/page-header.tsx +43 -0
- package/src/components/segmented-control.tsx +50 -0
- package/src/components/timeline.tsx +70 -0
- package/src/index.ts +15 -0
- package/src/stories/Banner.stories.tsx +28 -0
- package/src/stories/ComparisonSlider.stories.tsx +27 -0
- package/src/stories/DescriptionList.stories.tsx +26 -0
- package/src/stories/Marquee.stories.tsx +28 -0
- package/src/stories/PageHeader.stories.tsx +47 -0
- package/src/stories/SegmentedControl.stories.tsx +25 -0
- package/src/stories/Timeline.stories.tsx +28 -0
- package/tailwind.config.ts +2 -0
package/dist/index.d.ts
CHANGED
|
@@ -1295,7 +1295,143 @@ interface NativeSelectOptGroupProps extends React$1.OptgroupHTMLAttributes<HTMLO
|
|
|
1295
1295
|
}
|
|
1296
1296
|
declare const NativeSelectOptGroup: React$1.ForwardRefExoticComponent<NativeSelectOptGroupProps & React$1.RefAttributes<HTMLOptGroupElement>>;
|
|
1297
1297
|
|
|
1298
|
+
/**
|
|
1299
|
+
* DescriptionList — `<dl>` term/detail rows with the site's own spec-sheet
|
|
1300
|
+
* skin (mono, uppercase, tracked term labels; a divided rounded shell).
|
|
1301
|
+
* Lifted out of two hand-rolled call sites — `ComponentMeta`'s doc-page
|
|
1302
|
+
* spec strip and the homepage's "spec" card — into a reusable component.
|
|
1303
|
+
* Gap-fill addition (not in the original Figma source).
|
|
1304
|
+
*/
|
|
1305
|
+
declare const DescriptionList: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLDListElement> & React$1.RefAttributes<HTMLDListElement>>;
|
|
1306
|
+
interface DescriptionListItemProps extends Omit<React$1.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
1307
|
+
term: React$1.ReactNode;
|
|
1308
|
+
children: React$1.ReactNode;
|
|
1309
|
+
/** `row` (default) puts the term and value side by side; `stacked` puts the value below a full-width term — better for long values. */
|
|
1310
|
+
layout?: "row" | "stacked";
|
|
1311
|
+
}
|
|
1312
|
+
declare const DescriptionListItem: React$1.ForwardRefExoticComponent<DescriptionListItemProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Banner — a full-bleed, page-level notice (info/promo/maintenance),
|
|
1316
|
+
* optionally dismissible and stickied to the top of the viewport. Distinct
|
|
1317
|
+
* from `Alert` (in-flow, static) and `Sonner` (transient toast): Banner is
|
|
1318
|
+
* edge-to-edge and persistent until dismissed. Distinct from `Inform`
|
|
1319
|
+
* (contained, rounded inline card): Banner has no rounded corners or own
|
|
1320
|
+
* width — it spans whatever it's placed in, typically the full viewport.
|
|
1321
|
+
* Reuses `Inform`'s intent taxonomy and icon set for a consistent look.
|
|
1322
|
+
* Gap-fill addition (not in the original Figma source).
|
|
1323
|
+
*/
|
|
1324
|
+
declare const bannerVariants: (props?: ({
|
|
1325
|
+
variant?: "success" | "warning" | "error" | "information" | "action" | null | undefined;
|
|
1326
|
+
sticky?: boolean | null | undefined;
|
|
1327
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
1328
|
+
interface BannerProps extends React$1.HTMLAttributes<HTMLDivElement>, VariantProps<typeof bannerVariants> {
|
|
1329
|
+
hideIcon?: boolean;
|
|
1330
|
+
onDismiss?: () => void;
|
|
1331
|
+
/** optional CTA rendered inline after the message */
|
|
1332
|
+
action?: {
|
|
1333
|
+
label: React$1.ReactNode;
|
|
1334
|
+
onClick?: () => void;
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
declare const Banner: React$1.ForwardRefExoticComponent<BannerProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* SegmentedControl — an iOS-style single-select strip. Functionally
|
|
1341
|
+
* `ToggleGroup type="single"`: a thin, documented preset over the same
|
|
1342
|
+
* Radix primitive with the segmented look (`Tabs`' `TabsList`/
|
|
1343
|
+
* `TabsTrigger` visual treatment — filled track, raised active segment)
|
|
1344
|
+
* instead of `Toggle`'s individually-outlined-button look. `type="single"`
|
|
1345
|
+
* is fixed, not exposed — a segmented control that could go fully
|
|
1346
|
+
* unselected or multi-select isn't the pattern. Gap-fill addition (not in
|
|
1347
|
+
* the original Figma source).
|
|
1348
|
+
*/
|
|
1349
|
+
type SegmentedControlProps = Omit<ToggleGroupPrimitive.ToggleGroupSingleProps, "type">;
|
|
1350
|
+
declare const SegmentedControl: React$1.ForwardRefExoticComponent<SegmentedControlProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
1351
|
+
declare const SegmentedControlItem: React$1.ForwardRefExoticComponent<Omit<ToggleGroupPrimitive.ToggleGroupItemProps & React$1.RefAttributes<HTMLButtonElement>, "ref"> & React$1.RefAttributes<HTMLButtonElement>>;
|
|
1352
|
+
|
|
1353
|
+
/**
|
|
1354
|
+
* Timeline — ordered events down a rail (dot, connector, time, content).
|
|
1355
|
+
* `alternating` lays content left/right of a centered rail (desktop);
|
|
1356
|
+
* the default is a single left-aligned rail. Same rail/dot/connector
|
|
1357
|
+
* technique as `Stepper`, but for a history/activity log rather than a
|
|
1358
|
+
* progress indicator — no complete/current/upcoming states, an array of
|
|
1359
|
+
* arbitrary events instead. Gap-fill addition (not in the original Figma
|
|
1360
|
+
* source).
|
|
1361
|
+
*/
|
|
1362
|
+
interface TimelineItem {
|
|
1363
|
+
time?: React$1.ReactNode;
|
|
1364
|
+
title: React$1.ReactNode;
|
|
1365
|
+
content?: React$1.ReactNode;
|
|
1366
|
+
/** custom dot content — defaults to a filled circle */
|
|
1367
|
+
icon?: React$1.ReactNode;
|
|
1368
|
+
}
|
|
1369
|
+
interface TimelineProps extends React$1.HTMLAttributes<HTMLOListElement> {
|
|
1370
|
+
items: TimelineItem[];
|
|
1371
|
+
alternating?: boolean;
|
|
1372
|
+
}
|
|
1373
|
+
declare const Timeline: React$1.ForwardRefExoticComponent<TimelineProps & React$1.RefAttributes<HTMLOListElement>>;
|
|
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
|
+
|
|
1298
1434
|
/** Merges Tailwind class names, resolving conflicts (clsx + tailwind-merge). */
|
|
1299
1435
|
declare function cn(...inputs: ClassValue[]): string;
|
|
1300
1436
|
|
|
1301
|
-
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, 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, 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, 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, Toaster, type TocItem, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, badgeVariants, 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
|
@@ -4043,6 +4043,293 @@ var NativeSelectOptGroup = React69.forwardRef(
|
|
|
4043
4043
|
({ ...props }, ref) => /* @__PURE__ */ jsx74("optgroup", { ref, ...props })
|
|
4044
4044
|
);
|
|
4045
4045
|
NativeSelectOptGroup.displayName = "NativeSelectOptGroup";
|
|
4046
|
+
|
|
4047
|
+
// src/components/description-list.tsx
|
|
4048
|
+
import * as React70 from "react";
|
|
4049
|
+
import { jsx as jsx75, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
4050
|
+
var DescriptionList = React70.forwardRef(
|
|
4051
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx75(
|
|
4052
|
+
"dl",
|
|
4053
|
+
{
|
|
4054
|
+
ref,
|
|
4055
|
+
className: cn("divide-y divide-border rounded-lg border border-border bg-muted/20", className),
|
|
4056
|
+
...props
|
|
4057
|
+
}
|
|
4058
|
+
)
|
|
4059
|
+
);
|
|
4060
|
+
DescriptionList.displayName = "DescriptionList";
|
|
4061
|
+
var DescriptionListItem = React70.forwardRef(
|
|
4062
|
+
({ className, term, children, layout = "row", ...props }, ref) => /* @__PURE__ */ jsxs42(
|
|
4063
|
+
"div",
|
|
4064
|
+
{
|
|
4065
|
+
ref,
|
|
4066
|
+
className: cn("gap-3 px-3.5 py-2.5", layout === "row" ? "flex items-baseline" : "flex flex-col", className),
|
|
4067
|
+
...props,
|
|
4068
|
+
children: [
|
|
4069
|
+
/* @__PURE__ */ jsx75(
|
|
4070
|
+
"dt",
|
|
4071
|
+
{
|
|
4072
|
+
className: cn(
|
|
4073
|
+
"shrink-0 font-mono text-[10px] uppercase tracking-[0.14em] text-muted-foreground",
|
|
4074
|
+
layout === "row" && "w-24"
|
|
4075
|
+
),
|
|
4076
|
+
children: term
|
|
4077
|
+
}
|
|
4078
|
+
),
|
|
4079
|
+
/* @__PURE__ */ jsx75("dd", { className: "min-w-0 text-body-sm text-foreground", children })
|
|
4080
|
+
]
|
|
4081
|
+
}
|
|
4082
|
+
)
|
|
4083
|
+
);
|
|
4084
|
+
DescriptionListItem.displayName = "DescriptionListItem";
|
|
4085
|
+
|
|
4086
|
+
// src/components/banner.tsx
|
|
4087
|
+
import * as React71 from "react";
|
|
4088
|
+
import { cva as cva20 } from "class-variance-authority";
|
|
4089
|
+
import { CircleAlert as CircleAlert3, CircleCheck as CircleCheck3, Info as Info3, TriangleAlert as TriangleAlert4, X as X8 } from "lucide-react";
|
|
4090
|
+
import { jsx as jsx76, jsxs as jsxs43 } from "react/jsx-runtime";
|
|
4091
|
+
var bannerVariants = cva20(
|
|
4092
|
+
"flex w-full items-start gap-2.5 border-b px-4 py-3 text-body-sm font-sans [&>svg]:mt-0.5 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
4093
|
+
{
|
|
4094
|
+
variants: {
|
|
4095
|
+
variant: {
|
|
4096
|
+
information: "border-info/20 bg-info/10 text-info",
|
|
4097
|
+
warning: "border-warning/25 bg-warning/15 text-warning",
|
|
4098
|
+
success: "border-success/25 bg-success/15 text-success",
|
|
4099
|
+
error: "border-destructive/20 bg-destructive/10 text-destructive",
|
|
4100
|
+
action: "border-transparent bg-foreground text-background"
|
|
4101
|
+
},
|
|
4102
|
+
sticky: {
|
|
4103
|
+
true: "sticky top-0 z-40",
|
|
4104
|
+
false: ""
|
|
4105
|
+
}
|
|
4106
|
+
},
|
|
4107
|
+
defaultVariants: { variant: "information", sticky: false }
|
|
4108
|
+
}
|
|
4109
|
+
);
|
|
4110
|
+
var ICON2 = {
|
|
4111
|
+
information: Info3,
|
|
4112
|
+
warning: TriangleAlert4,
|
|
4113
|
+
success: CircleCheck3,
|
|
4114
|
+
error: CircleAlert3,
|
|
4115
|
+
action: Info3
|
|
4116
|
+
};
|
|
4117
|
+
var Banner = React71.forwardRef(
|
|
4118
|
+
({ className, variant = "information", sticky, hideIcon, onDismiss, action, children, ...props }, ref) => {
|
|
4119
|
+
const Icon2 = ICON2[variant ?? "information"];
|
|
4120
|
+
return /* @__PURE__ */ jsxs43("div", { ref, role: "banner", className: cn(bannerVariants({ variant, sticky }), className), ...props, children: [
|
|
4121
|
+
!hideIcon && /* @__PURE__ */ jsx76(Icon2, {}),
|
|
4122
|
+
/* @__PURE__ */ jsxs43("div", { className: "mx-auto flex min-w-0 max-w-4xl flex-1 flex-wrap items-center justify-center gap-x-3 gap-y-1 text-center", children: [
|
|
4123
|
+
/* @__PURE__ */ jsx76("span", { children }),
|
|
4124
|
+
action && /* @__PURE__ */ jsx76(
|
|
4125
|
+
"button",
|
|
4126
|
+
{
|
|
4127
|
+
type: "button",
|
|
4128
|
+
onClick: action.onClick,
|
|
4129
|
+
className: "inline-flex shrink-0 items-center gap-1.5 text-label-md font-medium underline-offset-2 outline-none hover:underline focus-visible:underline",
|
|
4130
|
+
children: action.label
|
|
4131
|
+
}
|
|
4132
|
+
)
|
|
4133
|
+
] }),
|
|
4134
|
+
onDismiss && /* @__PURE__ */ jsx76(
|
|
4135
|
+
"button",
|
|
4136
|
+
{
|
|
4137
|
+
type: "button",
|
|
4138
|
+
onClick: onDismiss,
|
|
4139
|
+
"aria-label": "Dismiss",
|
|
4140
|
+
className: "-m-0.5 shrink-0 rounded-[2px] p-0.5 opacity-muted outline-none transition-opacity hover:opacity-visible focus-visible:opacity-visible focus-visible:ring-1 focus-visible:ring-current",
|
|
4141
|
+
children: /* @__PURE__ */ jsx76(X8, { className: "size-3.5" })
|
|
4142
|
+
}
|
|
4143
|
+
)
|
|
4144
|
+
] });
|
|
4145
|
+
}
|
|
4146
|
+
);
|
|
4147
|
+
Banner.displayName = "Banner";
|
|
4148
|
+
|
|
4149
|
+
// src/components/segmented-control.tsx
|
|
4150
|
+
import * as React72 from "react";
|
|
4151
|
+
import * as ToggleGroupPrimitive2 from "@radix-ui/react-toggle-group";
|
|
4152
|
+
import { jsx as jsx77 } from "react/jsx-runtime";
|
|
4153
|
+
var SegmentedControl = React72.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx77(
|
|
4154
|
+
ToggleGroupPrimitive2.Root,
|
|
4155
|
+
{
|
|
4156
|
+
ref,
|
|
4157
|
+
type: "single",
|
|
4158
|
+
className: cn("inline-flex items-center rounded-lg bg-muted p-1 font-sans", className),
|
|
4159
|
+
...props
|
|
4160
|
+
}
|
|
4161
|
+
));
|
|
4162
|
+
SegmentedControl.displayName = "SegmentedControl";
|
|
4163
|
+
var SegmentedControlItem = React72.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx77(
|
|
4164
|
+
ToggleGroupPrimitive2.Item,
|
|
4165
|
+
{
|
|
4166
|
+
ref,
|
|
4167
|
+
className: cn(
|
|
4168
|
+
"inline-flex flex-1 items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-label-md font-medium text-muted-foreground transition-all",
|
|
4169
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
4170
|
+
"disabled:pointer-events-none disabled:opacity-disabled",
|
|
4171
|
+
"data-[state=on]:bg-background data-[state=on]:text-foreground data-[state=on]:shadow-sm",
|
|
4172
|
+
className
|
|
4173
|
+
),
|
|
4174
|
+
...props
|
|
4175
|
+
}
|
|
4176
|
+
));
|
|
4177
|
+
SegmentedControlItem.displayName = "SegmentedControlItem";
|
|
4178
|
+
|
|
4179
|
+
// src/components/timeline.tsx
|
|
4180
|
+
import * as React73 from "react";
|
|
4181
|
+
import { jsx as jsx78, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
4182
|
+
var Body = ({ item, align }) => /* @__PURE__ */ jsxs44("div", { className: cn("pb-6", align === "right" && "text-right"), children: [
|
|
4183
|
+
item.time && /* @__PURE__ */ jsx78("p", { className: "text-label-sm text-muted-foreground", children: item.time }),
|
|
4184
|
+
/* @__PURE__ */ jsx78("p", { className: "text-label-md font-medium text-foreground", children: item.title }),
|
|
4185
|
+
item.content && /* @__PURE__ */ jsx78("div", { className: "text-body-sm text-muted-foreground", children: item.content })
|
|
4186
|
+
] });
|
|
4187
|
+
var Rail = ({ item, isLast }) => /* @__PURE__ */ jsxs44("div", { className: "flex flex-col items-center", children: [
|
|
4188
|
+
/* @__PURE__ */ jsx78("span", { className: "flex size-2.5 shrink-0 items-center justify-center rounded-full bg-primary", children: item.icon }),
|
|
4189
|
+
!isLast && /* @__PURE__ */ jsx78("span", { "aria-hidden": true, className: "my-1 w-px flex-1 bg-border" })
|
|
4190
|
+
] });
|
|
4191
|
+
var Timeline = React73.forwardRef(
|
|
4192
|
+
({ className, items, alternating = false, ...props }, ref) => /* @__PURE__ */ jsx78("ol", { ref, className: cn("flex flex-col font-sans", className), ...props, children: items.map((item, i) => {
|
|
4193
|
+
const isLast = i === items.length - 1;
|
|
4194
|
+
if (!alternating) {
|
|
4195
|
+
return /* @__PURE__ */ jsxs44("li", { className: "grid grid-cols-[auto_1fr] gap-x-3", children: [
|
|
4196
|
+
/* @__PURE__ */ jsx78(Rail, { item, isLast }),
|
|
4197
|
+
/* @__PURE__ */ jsx78(Body, { item, align: "left" })
|
|
4198
|
+
] }, i);
|
|
4199
|
+
}
|
|
4200
|
+
const onRight = i % 2 === 0;
|
|
4201
|
+
return /* @__PURE__ */ jsxs44("li", { className: "grid grid-cols-[1fr_auto_1fr] gap-x-4", children: [
|
|
4202
|
+
onRight ? /* @__PURE__ */ jsx78("div", {}) : /* @__PURE__ */ jsx78(Body, { item, align: "right" }),
|
|
4203
|
+
/* @__PURE__ */ jsx78(Rail, { item, isLast }),
|
|
4204
|
+
onRight ? /* @__PURE__ */ jsx78(Body, { item, align: "left" }) : /* @__PURE__ */ jsx78("div", {})
|
|
4205
|
+
] }, i);
|
|
4206
|
+
}) })
|
|
4207
|
+
);
|
|
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";
|
|
4046
4333
|
export {
|
|
4047
4334
|
Accordion,
|
|
4048
4335
|
AccordionContent,
|
|
@@ -4074,6 +4361,7 @@ export {
|
|
|
4074
4361
|
AvatarGroup,
|
|
4075
4362
|
AvatarImage,
|
|
4076
4363
|
Badge,
|
|
4364
|
+
Banner,
|
|
4077
4365
|
Breadcrumb,
|
|
4078
4366
|
BreadcrumbEllipsis,
|
|
4079
4367
|
BreadcrumbItem,
|
|
@@ -4118,6 +4406,7 @@ export {
|
|
|
4118
4406
|
CommandList,
|
|
4119
4407
|
CommandSeparator,
|
|
4120
4408
|
CommandShortcut,
|
|
4409
|
+
ComparisonSlider,
|
|
4121
4410
|
ContextMenu,
|
|
4122
4411
|
ContextMenuCheckboxItem,
|
|
4123
4412
|
ContextMenuContent,
|
|
@@ -4135,6 +4424,8 @@ export {
|
|
|
4135
4424
|
ContextMenuTrigger,
|
|
4136
4425
|
DataTable,
|
|
4137
4426
|
DatePicker,
|
|
4427
|
+
DescriptionList,
|
|
4428
|
+
DescriptionListItem,
|
|
4138
4429
|
Dialog,
|
|
4139
4430
|
DialogClose,
|
|
4140
4431
|
DialogContent,
|
|
@@ -4215,6 +4506,7 @@ export {
|
|
|
4215
4506
|
Label2 as Label,
|
|
4216
4507
|
List3 as List,
|
|
4217
4508
|
ListItem,
|
|
4509
|
+
Marquee,
|
|
4218
4510
|
Menubar,
|
|
4219
4511
|
MenubarCheckboxItem,
|
|
4220
4512
|
MenubarContent,
|
|
@@ -4246,6 +4538,7 @@ export {
|
|
|
4246
4538
|
NavigationMenuTrigger,
|
|
4247
4539
|
NavigationMenuViewport,
|
|
4248
4540
|
NumberInput,
|
|
4541
|
+
PageHeader,
|
|
4249
4542
|
Pagination,
|
|
4250
4543
|
PaginationContent,
|
|
4251
4544
|
PaginationEllipsis,
|
|
@@ -4268,6 +4561,8 @@ export {
|
|
|
4268
4561
|
ResizablePanelGroup,
|
|
4269
4562
|
ScrollArea,
|
|
4270
4563
|
ScrollBar,
|
|
4564
|
+
SegmentedControl,
|
|
4565
|
+
SegmentedControlItem,
|
|
4271
4566
|
Select,
|
|
4272
4567
|
SelectContent,
|
|
4273
4568
|
SelectGroup,
|
|
@@ -4325,6 +4620,7 @@ export {
|
|
|
4325
4620
|
TabsTrigger,
|
|
4326
4621
|
Tag,
|
|
4327
4622
|
Textarea,
|
|
4623
|
+
Timeline,
|
|
4328
4624
|
Toaster,
|
|
4329
4625
|
Toggle,
|
|
4330
4626
|
ToggleGroup,
|
|
@@ -4334,6 +4630,7 @@ export {
|
|
|
4334
4630
|
TooltipProvider,
|
|
4335
4631
|
TooltipTrigger,
|
|
4336
4632
|
badgeVariants,
|
|
4633
|
+
bannerVariants,
|
|
4337
4634
|
buttonVariants,
|
|
4338
4635
|
cn,
|
|
4339
4636
|
emptyMediaVariants,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kinetixui/ui",
|
|
3
|
-
"version": "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.
|
|
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,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
import { CircleAlert, CircleCheck, Info, TriangleAlert, X } from "lucide-react";
|
|
6
|
+
import { cn } from "../lib/utils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Banner — a full-bleed, page-level notice (info/promo/maintenance),
|
|
10
|
+
* optionally dismissible and stickied to the top of the viewport. Distinct
|
|
11
|
+
* from `Alert` (in-flow, static) and `Sonner` (transient toast): Banner is
|
|
12
|
+
* edge-to-edge and persistent until dismissed. Distinct from `Inform`
|
|
13
|
+
* (contained, rounded inline card): Banner has no rounded corners or own
|
|
14
|
+
* width — it spans whatever it's placed in, typically the full viewport.
|
|
15
|
+
* Reuses `Inform`'s intent taxonomy and icon set for a consistent look.
|
|
16
|
+
* Gap-fill addition (not in the original Figma source).
|
|
17
|
+
*/
|
|
18
|
+
const bannerVariants = cva(
|
|
19
|
+
"flex w-full items-start gap-2.5 border-b px-4 py-3 text-body-sm font-sans [&>svg]:mt-0.5 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
20
|
+
{
|
|
21
|
+
variants: {
|
|
22
|
+
variant: {
|
|
23
|
+
information: "border-info/20 bg-info/10 text-info",
|
|
24
|
+
warning: "border-warning/25 bg-warning/15 text-warning",
|
|
25
|
+
success: "border-success/25 bg-success/15 text-success",
|
|
26
|
+
error: "border-destructive/20 bg-destructive/10 text-destructive",
|
|
27
|
+
action: "border-transparent bg-foreground text-background",
|
|
28
|
+
},
|
|
29
|
+
sticky: {
|
|
30
|
+
true: "sticky top-0 z-40",
|
|
31
|
+
false: "",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: { variant: "information", sticky: false },
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const ICON = {
|
|
39
|
+
information: Info,
|
|
40
|
+
warning: TriangleAlert,
|
|
41
|
+
success: CircleCheck,
|
|
42
|
+
error: CircleAlert,
|
|
43
|
+
action: Info,
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
export interface BannerProps
|
|
47
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
48
|
+
VariantProps<typeof bannerVariants> {
|
|
49
|
+
hideIcon?: boolean;
|
|
50
|
+
onDismiss?: () => void;
|
|
51
|
+
/** optional CTA rendered inline after the message */
|
|
52
|
+
action?: { label: React.ReactNode; onClick?: () => void };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const Banner = React.forwardRef<HTMLDivElement, BannerProps>(
|
|
56
|
+
({ className, variant = "information", sticky, hideIcon, onDismiss, action, children, ...props }, ref) => {
|
|
57
|
+
const Icon = ICON[variant ?? "information"];
|
|
58
|
+
return (
|
|
59
|
+
<div ref={ref} role="banner" className={cn(bannerVariants({ variant, sticky }), className)} {...props}>
|
|
60
|
+
{!hideIcon && <Icon />}
|
|
61
|
+
<div className="mx-auto flex min-w-0 max-w-4xl flex-1 flex-wrap items-center justify-center gap-x-3 gap-y-1 text-center">
|
|
62
|
+
<span>{children}</span>
|
|
63
|
+
{action && (
|
|
64
|
+
<button
|
|
65
|
+
type="button"
|
|
66
|
+
onClick={action.onClick}
|
|
67
|
+
className="inline-flex shrink-0 items-center gap-1.5 text-label-md font-medium underline-offset-2 outline-none hover:underline focus-visible:underline"
|
|
68
|
+
>
|
|
69
|
+
{action.label}
|
|
70
|
+
</button>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
{onDismiss && (
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
onClick={onDismiss}
|
|
77
|
+
aria-label="Dismiss"
|
|
78
|
+
className="-m-0.5 shrink-0 rounded-[2px] p-0.5 opacity-muted outline-none transition-opacity hover:opacity-visible focus-visible:opacity-visible focus-visible:ring-1 focus-visible:ring-current"
|
|
79
|
+
>
|
|
80
|
+
<X className="size-3.5" />
|
|
81
|
+
</button>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
Banner.displayName = "Banner";
|
|
88
|
+
|
|
89
|
+
export { Banner, bannerVariants };
|
|
@@ -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,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* DescriptionList — `<dl>` term/detail rows with the site's own spec-sheet
|
|
8
|
+
* skin (mono, uppercase, tracked term labels; a divided rounded shell).
|
|
9
|
+
* Lifted out of two hand-rolled call sites — `ComponentMeta`'s doc-page
|
|
10
|
+
* spec strip and the homepage's "spec" card — into a reusable component.
|
|
11
|
+
* Gap-fill addition (not in the original Figma source).
|
|
12
|
+
*/
|
|
13
|
+
const DescriptionList = React.forwardRef<HTMLDListElement, React.HTMLAttributes<HTMLDListElement>>(
|
|
14
|
+
({ className, ...props }, ref) => (
|
|
15
|
+
<dl
|
|
16
|
+
ref={ref}
|
|
17
|
+
className={cn("divide-y divide-border rounded-lg border border-border bg-muted/20", className)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
),
|
|
21
|
+
);
|
|
22
|
+
DescriptionList.displayName = "DescriptionList";
|
|
23
|
+
|
|
24
|
+
export interface DescriptionListItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
25
|
+
term: React.ReactNode;
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
/** `row` (default) puts the term and value side by side; `stacked` puts the value below a full-width term — better for long values. */
|
|
28
|
+
layout?: "row" | "stacked";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DescriptionListItem = React.forwardRef<HTMLDivElement, DescriptionListItemProps>(
|
|
32
|
+
({ className, term, children, layout = "row", ...props }, ref) => (
|
|
33
|
+
<div
|
|
34
|
+
ref={ref}
|
|
35
|
+
className={cn("gap-3 px-3.5 py-2.5", layout === "row" ? "flex items-baseline" : "flex flex-col", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
<dt
|
|
39
|
+
className={cn(
|
|
40
|
+
"shrink-0 font-mono text-[10px] uppercase tracking-[0.14em] text-muted-foreground",
|
|
41
|
+
layout === "row" && "w-24",
|
|
42
|
+
)}
|
|
43
|
+
>
|
|
44
|
+
{term}
|
|
45
|
+
</dt>
|
|
46
|
+
<dd className="min-w-0 text-body-sm text-foreground">{children}</dd>
|
|
47
|
+
</div>
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
DescriptionListItem.displayName = "DescriptionListItem";
|
|
51
|
+
|
|
52
|
+
export { DescriptionList, DescriptionListItem };
|
|
@@ -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 };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SegmentedControl — an iOS-style single-select strip. Functionally
|
|
9
|
+
* `ToggleGroup type="single"`: a thin, documented preset over the same
|
|
10
|
+
* Radix primitive with the segmented look (`Tabs`' `TabsList`/
|
|
11
|
+
* `TabsTrigger` visual treatment — filled track, raised active segment)
|
|
12
|
+
* instead of `Toggle`'s individually-outlined-button look. `type="single"`
|
|
13
|
+
* is fixed, not exposed — a segmented control that could go fully
|
|
14
|
+
* unselected or multi-select isn't the pattern. Gap-fill addition (not in
|
|
15
|
+
* the original Figma source).
|
|
16
|
+
*/
|
|
17
|
+
export type SegmentedControlProps = Omit<ToggleGroupPrimitive.ToggleGroupSingleProps, "type">;
|
|
18
|
+
|
|
19
|
+
const SegmentedControl = React.forwardRef<
|
|
20
|
+
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
|
21
|
+
SegmentedControlProps
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<ToggleGroupPrimitive.Root
|
|
24
|
+
ref={ref}
|
|
25
|
+
type="single"
|
|
26
|
+
className={cn("inline-flex items-center rounded-lg bg-muted p-1 font-sans", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
));
|
|
30
|
+
SegmentedControl.displayName = "SegmentedControl";
|
|
31
|
+
|
|
32
|
+
const SegmentedControlItem = React.forwardRef<
|
|
33
|
+
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
|
34
|
+
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item>
|
|
35
|
+
>(({ className, ...props }, ref) => (
|
|
36
|
+
<ToggleGroupPrimitive.Item
|
|
37
|
+
ref={ref}
|
|
38
|
+
className={cn(
|
|
39
|
+
"inline-flex flex-1 items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-label-md font-medium text-muted-foreground transition-all",
|
|
40
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
41
|
+
"disabled:pointer-events-none disabled:opacity-disabled",
|
|
42
|
+
"data-[state=on]:bg-background data-[state=on]:text-foreground data-[state=on]:shadow-sm",
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
));
|
|
48
|
+
SegmentedControlItem.displayName = "SegmentedControlItem";
|
|
49
|
+
|
|
50
|
+
export { SegmentedControl, SegmentedControlItem };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Timeline — ordered events down a rail (dot, connector, time, content).
|
|
8
|
+
* `alternating` lays content left/right of a centered rail (desktop);
|
|
9
|
+
* the default is a single left-aligned rail. Same rail/dot/connector
|
|
10
|
+
* technique as `Stepper`, but for a history/activity log rather than a
|
|
11
|
+
* progress indicator — no complete/current/upcoming states, an array of
|
|
12
|
+
* arbitrary events instead. Gap-fill addition (not in the original Figma
|
|
13
|
+
* source).
|
|
14
|
+
*/
|
|
15
|
+
export interface TimelineItem {
|
|
16
|
+
time?: React.ReactNode;
|
|
17
|
+
title: React.ReactNode;
|
|
18
|
+
content?: React.ReactNode;
|
|
19
|
+
/** custom dot content — defaults to a filled circle */
|
|
20
|
+
icon?: React.ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TimelineProps extends React.HTMLAttributes<HTMLOListElement> {
|
|
24
|
+
items: TimelineItem[];
|
|
25
|
+
alternating?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const Body = ({ item, align }: { item: TimelineItem; align: "left" | "right" }) => (
|
|
29
|
+
<div className={cn("pb-6", align === "right" && "text-right")}>
|
|
30
|
+
{item.time && <p className="text-label-sm text-muted-foreground">{item.time}</p>}
|
|
31
|
+
<p className="text-label-md font-medium text-foreground">{item.title}</p>
|
|
32
|
+
{item.content && <div className="text-body-sm text-muted-foreground">{item.content}</div>}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const Rail = ({ item, isLast }: { item: TimelineItem; isLast: boolean }) => (
|
|
37
|
+
<div className="flex flex-col items-center">
|
|
38
|
+
<span className="flex size-2.5 shrink-0 items-center justify-center rounded-full bg-primary">{item.icon}</span>
|
|
39
|
+
{!isLast && <span aria-hidden className="my-1 w-px flex-1 bg-border" />}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const Timeline = React.forwardRef<HTMLOListElement, TimelineProps>(
|
|
44
|
+
({ className, items, alternating = false, ...props }, ref) => (
|
|
45
|
+
<ol ref={ref} className={cn("flex flex-col font-sans", className)} {...props}>
|
|
46
|
+
{items.map((item, i) => {
|
|
47
|
+
const isLast = i === items.length - 1;
|
|
48
|
+
if (!alternating) {
|
|
49
|
+
return (
|
|
50
|
+
<li key={i} className="grid grid-cols-[auto_1fr] gap-x-3">
|
|
51
|
+
<Rail item={item} isLast={isLast} />
|
|
52
|
+
<Body item={item} align="left" />
|
|
53
|
+
</li>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const onRight = i % 2 === 0;
|
|
57
|
+
return (
|
|
58
|
+
<li key={i} className="grid grid-cols-[1fr_auto_1fr] gap-x-4">
|
|
59
|
+
{onRight ? <div /> : <Body item={item} align="right" />}
|
|
60
|
+
<Rail item={item} isLast={isLast} />
|
|
61
|
+
{onRight ? <Body item={item} align="left" /> : <div />}
|
|
62
|
+
</li>
|
|
63
|
+
);
|
|
64
|
+
})}
|
|
65
|
+
</ol>
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
Timeline.displayName = "Timeline";
|
|
69
|
+
|
|
70
|
+
export { Timeline };
|
package/src/index.ts
CHANGED
|
@@ -339,5 +339,20 @@ export {
|
|
|
339
339
|
type NativeSelectOptionProps,
|
|
340
340
|
type NativeSelectOptGroupProps,
|
|
341
341
|
} from "./components/native-select";
|
|
342
|
+
export {
|
|
343
|
+
DescriptionList,
|
|
344
|
+
DescriptionListItem,
|
|
345
|
+
type DescriptionListItemProps,
|
|
346
|
+
} from "./components/description-list";
|
|
347
|
+
export { Banner, bannerVariants, type BannerProps } from "./components/banner";
|
|
348
|
+
export {
|
|
349
|
+
SegmentedControl,
|
|
350
|
+
SegmentedControlItem,
|
|
351
|
+
type SegmentedControlProps,
|
|
352
|
+
} from "./components/segmented-control";
|
|
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";
|
|
342
357
|
|
|
343
358
|
export { cn } from "./lib/utils";
|
|
@@ -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 { Banner } from "@kinetixui/ui";
|
|
6
|
+
|
|
7
|
+
const Demo = () => (
|
|
8
|
+
<div className="flex w-full flex-col gap-3">
|
|
9
|
+
<Banner variant="information" onDismiss={() => {}} action={{ label: "Learn more" }}>
|
|
10
|
+
A new version of KinetixUI is available.
|
|
11
|
+
</Banner>
|
|
12
|
+
<Banner variant="action" action={{ label: "Upgrade" }}>
|
|
13
|
+
You’re on the free plan — upgrade for unlimited projects.
|
|
14
|
+
</Banner>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const meta = {
|
|
19
|
+
title: "Feedback/Banner",
|
|
20
|
+
parameters: {
|
|
21
|
+
layout: "centered",
|
|
22
|
+
docs: { source: { code: "<Banner variant=\"information\" onDismiss={close} action={{ label: \"Learn more\" }}>\\n A new version of KinetixUI is available.\\n</Banner>", language: "tsx" } },
|
|
23
|
+
},
|
|
24
|
+
} satisfies Meta;
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
|
|
28
|
+
export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
|
|
@@ -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,26 @@
|
|
|
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 { Avatar, DescriptionList, DescriptionListItem } from "@kinetixui/ui";
|
|
6
|
+
|
|
7
|
+
const Demo = () => (
|
|
8
|
+
<DescriptionList className="w-full max-w-sm">
|
|
9
|
+
<DescriptionListItem term="Category">Data Display</DescriptionListItem>
|
|
10
|
+
<DescriptionListItem term="Built on">Radix Avatar</DescriptionListItem>
|
|
11
|
+
<DescriptionListItem term="Platforms">React, SwiftUI, Compose, Flutter</DescriptionListItem>
|
|
12
|
+
<DescriptionListItem term="CLI">npx @kinetixui/cli add avatar</DescriptionListItem>
|
|
13
|
+
</DescriptionList>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const meta = {
|
|
17
|
+
title: "Data Display/DescriptionList",
|
|
18
|
+
parameters: {
|
|
19
|
+
layout: "centered",
|
|
20
|
+
docs: { source: { code: "<DescriptionList>\\n <DescriptionListItem term=\"Category\">Data Display</DescriptionListItem>\\n <DescriptionListItem term=\"Built on\">Radix Avatar</DescriptionListItem>\\n</DescriptionList>", language: "tsx" } },
|
|
21
|
+
},
|
|
22
|
+
} satisfies Meta;
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
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 /> };
|
|
@@ -0,0 +1,25 @@
|
|
|
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 { List, SegmentedControl, SegmentedControlItem } from "@kinetixui/ui";
|
|
6
|
+
|
|
7
|
+
const Demo = () => (
|
|
8
|
+
<SegmentedControl defaultValue="grid" className="w-full max-w-xs">
|
|
9
|
+
<SegmentedControlItem value="list">List</SegmentedControlItem>
|
|
10
|
+
<SegmentedControlItem value="grid">Grid</SegmentedControlItem>
|
|
11
|
+
<SegmentedControlItem value="board">Board</SegmentedControlItem>
|
|
12
|
+
</SegmentedControl>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const meta = {
|
|
16
|
+
title: "Controls & Actions/SegmentedControl",
|
|
17
|
+
parameters: {
|
|
18
|
+
layout: "centered",
|
|
19
|
+
docs: { source: { code: "<SegmentedControl defaultValue=\"grid\">\\n <SegmentedControlItem value=\"list\">List</SegmentedControlItem>\\n <SegmentedControlItem value=\"grid\">Grid</SegmentedControlItem>\\n <SegmentedControlItem value=\"board\">Board</SegmentedControlItem>\\n</SegmentedControl>", language: "tsx" } },
|
|
20
|
+
},
|
|
21
|
+
} satisfies Meta;
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
|
|
25
|
+
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 { SegmentedControl, Timeline } from "@kinetixui/ui";
|
|
6
|
+
|
|
7
|
+
const Demo = () => (
|
|
8
|
+
<Timeline
|
|
9
|
+
className="w-full max-w-sm"
|
|
10
|
+
items={[
|
|
11
|
+
{ time: "2 hours ago", title: "Deployed to production", content: "v0.9.0 shipped." },
|
|
12
|
+
{ time: "Yesterday", title: "Opened PR #100", content: "feat(ui): add SegmentedControl" },
|
|
13
|
+
{ time: "3 days ago", title: "Project created" },
|
|
14
|
+
]}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const meta = {
|
|
19
|
+
title: "Data Display/Timeline",
|
|
20
|
+
parameters: {
|
|
21
|
+
layout: "centered",
|
|
22
|
+
docs: { source: { code: "<Timeline\\n items={[\\n { time: \"2 hours ago\", title: \"Deployed to production\", content: \"v0.9.0 shipped.\" },\\n { time: \"Yesterday\", title: \"Opened PR #100\" },\\n ]}\\n/>", language: "tsx" } },
|
|
23
|
+
},
|
|
24
|
+
} satisfies Meta;
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
|
|
28
|
+
export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
|
package/tailwind.config.ts
CHANGED
|
@@ -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
|
},
|