@kinetixui/ui 0.8.0 → 0.10.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
@@ -1252,7 +1252,127 @@ declare const EmptyTitle: React$1.ForwardRefExoticComponent<React$1.HTMLAttribut
1252
1252
  declare const EmptyDescription: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLParagraphElement> & React$1.RefAttributes<HTMLParagraphElement>>;
1253
1253
  declare const EmptyContent: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLDivElement> & React$1.RefAttributes<HTMLDivElement>>;
1254
1254
 
1255
+ /**
1256
+ * ButtonGroup — visually joins adjacent buttons into a connected cluster
1257
+ * (segmented-control style): inner corners squared off, shared borders
1258
+ * overlapped by 1px so they don't double up, the focused child raised
1259
+ * above its neighbours so its ring isn't clipped. Gap-fill addition (not
1260
+ * in the original Figma source) — matches the shadcn/ui ButtonGroup API
1261
+ * shape. Works with any child that renders its own border/radius (Button,
1262
+ * ButtonGroupText, a Select trigger, …), not just `<Button>`.
1263
+ */
1264
+ interface ButtonGroupProps extends React$1.HTMLAttributes<HTMLDivElement> {
1265
+ orientation?: "horizontal" | "vertical";
1266
+ }
1267
+ declare const ButtonGroup: React$1.ForwardRefExoticComponent<ButtonGroupProps & React$1.RefAttributes<HTMLDivElement>>;
1268
+ interface ButtonGroupSeparatorProps extends React$1.HTMLAttributes<HTMLDivElement> {
1269
+ orientation?: "horizontal" | "vertical";
1270
+ }
1271
+ /** A thin divider between segments — useful between non-outline buttons, where the group's own overlap trick has no shared border to lean on. */
1272
+ declare const ButtonGroupSeparator: React$1.ForwardRefExoticComponent<ButtonGroupSeparatorProps & React$1.RefAttributes<HTMLDivElement>>;
1273
+ /** A static, non-interactive label segment inside a group — e.g. a unit or a prefix next to steppers. */
1274
+ declare const ButtonGroupText: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLSpanElement> & React$1.RefAttributes<HTMLSpanElement>>;
1275
+
1276
+ /**
1277
+ * NativeSelect — a styled wrapper around the native `<select>` element,
1278
+ * for when a form should submit synchronously without JS (or just prefers
1279
+ * the OS-native picker on mobile) instead of Select's Radix popover.
1280
+ * Matches Input's `inputVariants` styling 1:1 so the two are drop-in
1281
+ * interchangeable in a Field. Gap-fill addition (not in the original
1282
+ * Figma source) — matches the shadcn/ui Native Select API shape.
1283
+ */
1284
+ declare const nativeSelectVariants: (props?: ({
1285
+ state?: "Default" | "Focus" | "Disabled" | "Error" | null | undefined;
1286
+ corners?: "sharp" | "default" | "pill" | "rounded" | null | undefined;
1287
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
1288
+ interface NativeSelectProps extends Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "size">, VariantProps<typeof nativeSelectVariants> {
1289
+ }
1290
+ declare const NativeSelect: React$1.ForwardRefExoticComponent<NativeSelectProps & React$1.RefAttributes<HTMLSelectElement>>;
1291
+ interface NativeSelectOptionProps extends React$1.OptionHTMLAttributes<HTMLOptionElement> {
1292
+ }
1293
+ declare const NativeSelectOption: React$1.ForwardRefExoticComponent<NativeSelectOptionProps & React$1.RefAttributes<HTMLOptionElement>>;
1294
+ interface NativeSelectOptGroupProps extends React$1.OptgroupHTMLAttributes<HTMLOptGroupElement> {
1295
+ }
1296
+ declare const NativeSelectOptGroup: React$1.ForwardRefExoticComponent<NativeSelectOptGroupProps & React$1.RefAttributes<HTMLOptGroupElement>>;
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
+
1255
1375
  /** Merges Tailwind class names, resolving conflicts (clsx + tailwind-merge). */
1256
1376
  declare function cn(...inputs: ClassValue[]): string;
1257
1377
 
1258
- 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, 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, 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, navigationMenuTriggerStyle, spinnerVariants, tagVariants, textareaVariants, toggleVariants, useAppBar, useFormField, useSidebar };
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 };
package/dist/index.js CHANGED
@@ -3935,6 +3935,277 @@ var EmptyContent = React67.forwardRef(
3935
3935
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx72("div", { ref, className: cn("flex flex-col items-center gap-3", className), ...props })
3936
3936
  );
3937
3937
  EmptyContent.displayName = "EmptyContent";
3938
+
3939
+ // src/components/button-group.tsx
3940
+ import * as React68 from "react";
3941
+ import { jsx as jsx73 } from "react/jsx-runtime";
3942
+ var ButtonGroup = React68.forwardRef(
3943
+ ({ className, orientation = "horizontal", ...props }, ref) => /* @__PURE__ */ jsx73(
3944
+ "div",
3945
+ {
3946
+ ref,
3947
+ role: "group",
3948
+ "data-orientation": orientation,
3949
+ className: cn(
3950
+ "flex w-fit items-stretch [&>*]:relative [&>*]:focus-visible:z-10",
3951
+ orientation === "horizontal" ? "flex-row [&>*:not(:first-child)]:-ml-px [&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none" : "flex-col [&>*:not(:first-child)]:-mt-px [&>*:not(:first-child)]:rounded-t-none [&>*:not(:last-child)]:rounded-b-none",
3952
+ className
3953
+ ),
3954
+ ...props
3955
+ }
3956
+ )
3957
+ );
3958
+ ButtonGroup.displayName = "ButtonGroup";
3959
+ var ButtonGroupSeparator = React68.forwardRef(
3960
+ ({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsx73(
3961
+ "div",
3962
+ {
3963
+ ref,
3964
+ role: "separator",
3965
+ "aria-orientation": orientation,
3966
+ className: cn("shrink-0 self-stretch bg-border", orientation === "vertical" ? "my-1.5 w-px" : "mx-1.5 h-px", className),
3967
+ ...props
3968
+ }
3969
+ )
3970
+ );
3971
+ ButtonGroupSeparator.displayName = "ButtonGroupSeparator";
3972
+ var ButtonGroupText = React68.forwardRef(
3973
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx73(
3974
+ "span",
3975
+ {
3976
+ ref,
3977
+ className: cn(
3978
+ "flex items-center gap-1.5 rounded-md border border-input bg-muted px-3 text-label-md text-foreground",
3979
+ className
3980
+ ),
3981
+ ...props
3982
+ }
3983
+ )
3984
+ );
3985
+ ButtonGroupText.displayName = "ButtonGroupText";
3986
+
3987
+ // src/components/native-select.tsx
3988
+ import * as React69 from "react";
3989
+ import { ChevronDown as ChevronDown4 } from "lucide-react";
3990
+ import { cva as cva19 } from "class-variance-authority";
3991
+ import { jsx as jsx74, jsxs as jsxs41 } from "react/jsx-runtime";
3992
+ var nativeSelectVariants = cva19(
3993
+ [
3994
+ "flex w-full appearance-none border border-input bg-background px-3 py-3 pr-9",
3995
+ "font-sans text-[14px] leading-5 tracking-[0.25px] text-foreground",
3996
+ "outline-none transition-colors",
3997
+ "focus-visible:border-primary focus-visible:shadow-focus",
3998
+ "disabled:cursor-not-allowed disabled:opacity-disabled",
3999
+ "aria-[invalid=true]:border-destructive aria-[invalid=true]:focus-visible:border-destructive aria-[invalid=true]:focus-visible:shadow-focus-destructive"
4000
+ ],
4001
+ {
4002
+ variants: {
4003
+ state: {
4004
+ Default: "",
4005
+ Focus: "border-primary shadow-focus",
4006
+ Error: "border-destructive",
4007
+ Disabled: "opacity-disabled pointer-events-none"
4008
+ },
4009
+ corners: {
4010
+ sharp: "rounded-none",
4011
+ default: "rounded-sm",
4012
+ rounded: "rounded-md",
4013
+ pill: "rounded-full px-4"
4014
+ }
4015
+ },
4016
+ defaultVariants: { state: "Default", corners: "default" }
4017
+ }
4018
+ );
4019
+ var NativeSelect = React69.forwardRef(
4020
+ ({ className, state, corners, disabled, "aria-invalid": ariaInvalid, children, ...props }, ref) => /* @__PURE__ */ jsxs41("div", { className: "relative", children: [
4021
+ /* @__PURE__ */ jsx74(
4022
+ "select",
4023
+ {
4024
+ ref,
4025
+ "data-slot": "native-select",
4026
+ "data-state": state ? state.toLowerCase() : void 0,
4027
+ "aria-invalid": ariaInvalid ?? (state === "Error" || void 0),
4028
+ disabled: disabled || state === "Disabled",
4029
+ className: cn(nativeSelectVariants({ state, corners }), className),
4030
+ ...props,
4031
+ children
4032
+ }
4033
+ ),
4034
+ /* @__PURE__ */ jsx74(ChevronDown4, { className: "pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" })
4035
+ ] })
4036
+ );
4037
+ NativeSelect.displayName = "NativeSelect";
4038
+ var NativeSelectOption = React69.forwardRef(
4039
+ ({ ...props }, ref) => /* @__PURE__ */ jsx74("option", { ref, ...props })
4040
+ );
4041
+ NativeSelectOption.displayName = "NativeSelectOption";
4042
+ var NativeSelectOptGroup = React69.forwardRef(
4043
+ ({ ...props }, ref) => /* @__PURE__ */ jsx74("optgroup", { ref, ...props })
4044
+ );
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";
3938
4209
  export {
3939
4210
  Accordion,
3940
4211
  AccordionContent,
@@ -3966,6 +4237,7 @@ export {
3966
4237
  AvatarGroup,
3967
4238
  AvatarImage,
3968
4239
  Badge,
4240
+ Banner,
3969
4241
  Breadcrumb,
3970
4242
  BreadcrumbEllipsis,
3971
4243
  BreadcrumbItem,
@@ -3974,6 +4246,9 @@ export {
3974
4246
  BreadcrumbPage,
3975
4247
  BreadcrumbSeparator,
3976
4248
  Button,
4249
+ ButtonGroup,
4250
+ ButtonGroupSeparator,
4251
+ ButtonGroupText,
3977
4252
  Calendar,
3978
4253
  Card,
3979
4254
  CardContent,
@@ -4024,6 +4299,8 @@ export {
4024
4299
  ContextMenuTrigger,
4025
4300
  DataTable,
4026
4301
  DatePicker,
4302
+ DescriptionList,
4303
+ DescriptionListItem,
4027
4304
  Dialog,
4028
4305
  DialogClose,
4029
4306
  DialogContent,
@@ -4122,6 +4399,9 @@ export {
4122
4399
  MenubarTrigger,
4123
4400
  Metric,
4124
4401
  Modal,
4402
+ NativeSelect,
4403
+ NativeSelectOptGroup,
4404
+ NativeSelectOption,
4125
4405
  NavigationBar,
4126
4406
  NavigationMenu,
4127
4407
  NavigationMenuContent,
@@ -4154,6 +4434,8 @@ export {
4154
4434
  ResizablePanelGroup,
4155
4435
  ScrollArea,
4156
4436
  ScrollBar,
4437
+ SegmentedControl,
4438
+ SegmentedControlItem,
4157
4439
  Select,
4158
4440
  SelectContent,
4159
4441
  SelectGroup,
@@ -4211,6 +4493,7 @@ export {
4211
4493
  TabsTrigger,
4212
4494
  Tag,
4213
4495
  Textarea,
4496
+ Timeline,
4214
4497
  Toaster,
4215
4498
  Toggle,
4216
4499
  ToggleGroup,
@@ -4220,6 +4503,7 @@ export {
4220
4503
  TooltipProvider,
4221
4504
  TooltipTrigger,
4222
4505
  badgeVariants,
4506
+ bannerVariants,
4223
4507
  buttonVariants,
4224
4508
  cn,
4225
4509
  emptyMediaVariants,
@@ -4228,6 +4512,7 @@ export {
4228
4512
  informVariants,
4229
4513
  inputVariants,
4230
4514
  messageVariants,
4515
+ nativeSelectVariants,
4231
4516
  navigationMenuTriggerStyle,
4232
4517
  spinnerVariants,
4233
4518
  tagVariants,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kinetixui/ui",
3
- "version": "0.8.0",
3
+ "version": "0.10.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.8.0",
93
+ "@kinetixui/tokens": "0.10.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,71 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "../lib/utils";
5
+
6
+ /**
7
+ * ButtonGroup — visually joins adjacent buttons into a connected cluster
8
+ * (segmented-control style): inner corners squared off, shared borders
9
+ * overlapped by 1px so they don't double up, the focused child raised
10
+ * above its neighbours so its ring isn't clipped. Gap-fill addition (not
11
+ * in the original Figma source) — matches the shadcn/ui ButtonGroup API
12
+ * shape. Works with any child that renders its own border/radius (Button,
13
+ * ButtonGroupText, a Select trigger, …), not just `<Button>`.
14
+ */
15
+ export interface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {
16
+ orientation?: "horizontal" | "vertical";
17
+ }
18
+
19
+ const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
20
+ ({ className, orientation = "horizontal", ...props }, ref) => (
21
+ <div
22
+ ref={ref}
23
+ role="group"
24
+ data-orientation={orientation}
25
+ className={cn(
26
+ "flex w-fit items-stretch [&>*]:relative [&>*]:focus-visible:z-10",
27
+ orientation === "horizontal"
28
+ ? "flex-row [&>*:not(:first-child)]:-ml-px [&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none"
29
+ : "flex-col [&>*:not(:first-child)]:-mt-px [&>*:not(:first-child)]:rounded-t-none [&>*:not(:last-child)]:rounded-b-none",
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ ),
35
+ );
36
+ ButtonGroup.displayName = "ButtonGroup";
37
+
38
+ export interface ButtonGroupSeparatorProps extends React.HTMLAttributes<HTMLDivElement> {
39
+ orientation?: "horizontal" | "vertical";
40
+ }
41
+
42
+ /** A thin divider between segments — useful between non-outline buttons, where the group's own overlap trick has no shared border to lean on. */
43
+ const ButtonGroupSeparator = React.forwardRef<HTMLDivElement, ButtonGroupSeparatorProps>(
44
+ ({ className, orientation = "vertical", ...props }, ref) => (
45
+ <div
46
+ ref={ref}
47
+ role="separator"
48
+ aria-orientation={orientation}
49
+ className={cn("shrink-0 self-stretch bg-border", orientation === "vertical" ? "my-1.5 w-px" : "mx-1.5 h-px", className)}
50
+ {...props}
51
+ />
52
+ ),
53
+ );
54
+ ButtonGroupSeparator.displayName = "ButtonGroupSeparator";
55
+
56
+ /** A static, non-interactive label segment inside a group — e.g. a unit or a prefix next to steppers. */
57
+ const ButtonGroupText = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
58
+ ({ className, ...props }, ref) => (
59
+ <span
60
+ ref={ref}
61
+ className={cn(
62
+ "flex items-center gap-1.5 rounded-md border border-input bg-muted px-3 text-label-md text-foreground",
63
+ className,
64
+ )}
65
+ {...props}
66
+ />
67
+ ),
68
+ );
69
+ ButtonGroupText.displayName = "ButtonGroupText";
70
+
71
+ export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText };
@@ -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,82 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ChevronDown } from "lucide-react";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+ import { cn } from "../lib/utils";
7
+
8
+ /**
9
+ * NativeSelect — a styled wrapper around the native `<select>` element,
10
+ * for when a form should submit synchronously without JS (or just prefers
11
+ * the OS-native picker on mobile) instead of Select's Radix popover.
12
+ * Matches Input's `inputVariants` styling 1:1 so the two are drop-in
13
+ * interchangeable in a Field. Gap-fill addition (not in the original
14
+ * Figma source) — matches the shadcn/ui Native Select API shape.
15
+ */
16
+ const nativeSelectVariants = cva(
17
+ [
18
+ "flex w-full appearance-none border border-input bg-background px-3 py-3 pr-9",
19
+ "font-sans text-[14px] leading-5 tracking-[0.25px] text-foreground",
20
+ "outline-none transition-colors",
21
+ "focus-visible:border-primary focus-visible:shadow-focus",
22
+ "disabled:cursor-not-allowed disabled:opacity-disabled",
23
+ "aria-[invalid=true]:border-destructive aria-[invalid=true]:focus-visible:border-destructive aria-[invalid=true]:focus-visible:shadow-focus-destructive",
24
+ ],
25
+ {
26
+ variants: {
27
+ state: {
28
+ Default: "",
29
+ Focus: "border-primary shadow-focus",
30
+ Error: "border-destructive",
31
+ Disabled: "opacity-disabled pointer-events-none",
32
+ },
33
+ corners: {
34
+ sharp: "rounded-none",
35
+ default: "rounded-sm",
36
+ rounded: "rounded-md",
37
+ pill: "rounded-full px-4",
38
+ },
39
+ },
40
+ defaultVariants: { state: "Default", corners: "default" },
41
+ },
42
+ );
43
+
44
+ export interface NativeSelectProps
45
+ extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "size">,
46
+ VariantProps<typeof nativeSelectVariants> {}
47
+
48
+ const NativeSelect = React.forwardRef<HTMLSelectElement, NativeSelectProps>(
49
+ ({ className, state, corners, disabled, "aria-invalid": ariaInvalid, children, ...props }, ref) => (
50
+ <div className="relative">
51
+ <select
52
+ ref={ref}
53
+ data-slot="native-select"
54
+ data-state={state ? state.toLowerCase() : undefined}
55
+ aria-invalid={ariaInvalid ?? (state === "Error" || undefined)}
56
+ disabled={disabled || state === "Disabled"}
57
+ className={cn(nativeSelectVariants({ state, corners }), className)}
58
+ {...props}
59
+ >
60
+ {children}
61
+ </select>
62
+ <ChevronDown className="pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
63
+ </div>
64
+ ),
65
+ );
66
+ NativeSelect.displayName = "NativeSelect";
67
+
68
+ export interface NativeSelectOptionProps extends React.OptionHTMLAttributes<HTMLOptionElement> {}
69
+
70
+ const NativeSelectOption = React.forwardRef<HTMLOptionElement, NativeSelectOptionProps>(
71
+ ({ ...props }, ref) => <option ref={ref} {...props} />,
72
+ );
73
+ NativeSelectOption.displayName = "NativeSelectOption";
74
+
75
+ export interface NativeSelectOptGroupProps extends React.OptgroupHTMLAttributes<HTMLOptGroupElement> {}
76
+
77
+ const NativeSelectOptGroup = React.forwardRef<HTMLOptGroupElement, NativeSelectOptGroupProps>(
78
+ ({ ...props }, ref) => <optgroup ref={ref} {...props} />,
79
+ );
80
+ NativeSelectOptGroup.displayName = "NativeSelectOptGroup";
81
+
82
+ export { NativeSelect, NativeSelectOption, NativeSelectOptGroup, nativeSelectVariants };
@@ -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
@@ -323,5 +323,33 @@ export {
323
323
  emptyMediaVariants,
324
324
  type EmptyMediaProps,
325
325
  } from "./components/empty";
326
+ export {
327
+ ButtonGroup,
328
+ ButtonGroupSeparator,
329
+ ButtonGroupText,
330
+ type ButtonGroupProps,
331
+ type ButtonGroupSeparatorProps,
332
+ } from "./components/button-group";
333
+ export {
334
+ NativeSelect,
335
+ NativeSelectOption,
336
+ NativeSelectOptGroup,
337
+ nativeSelectVariants,
338
+ type NativeSelectProps,
339
+ type NativeSelectOptionProps,
340
+ type NativeSelectOptGroupProps,
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";
326
354
 
327
355
  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&rsquo;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,32 @@
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 { Button, ButtonGroup } from "@kinetixui/ui";
6
+ import { Bold, Italic, Underline } from "lucide-react";
7
+
8
+ const Demo = () => (
9
+ <ButtonGroup>
10
+ <Button variant="Outline" size="icon" aria-label="Bold">
11
+ <Bold />
12
+ </Button>
13
+ <Button variant="Outline" size="icon" aria-label="Italic">
14
+ <Italic />
15
+ </Button>
16
+ <Button variant="Outline" size="icon" aria-label="Underline">
17
+ <Underline />
18
+ </Button>
19
+ </ButtonGroup>
20
+ );
21
+
22
+ const meta = {
23
+ title: "Controls & Actions/ButtonGroup",
24
+ parameters: {
25
+ layout: "centered",
26
+ docs: { source: { code: "<ButtonGroup>\\n <Button variant=\"Outline\" size=\"icon\"><Bold /></Button>\\n <Button variant=\"Outline\" size=\"icon\"><Italic /></Button>\\n <Button variant=\"Outline\" size=\"icon\"><Underline /></Button>\\n</ButtonGroup>", language: "tsx" } },
27
+ },
28
+ } satisfies Meta;
29
+
30
+ export default meta;
31
+
32
+ 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,33 @@
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 { NativeSelect, NativeSelectOptGroup, NativeSelectOption, Select } from "@kinetixui/ui";
6
+
7
+ const Demo = () => (
8
+ <NativeSelect className="max-w-xs" defaultValue="">
9
+ <NativeSelectOption value="" disabled>
10
+ Select a fruit…
11
+ </NativeSelectOption>
12
+ <NativeSelectOptGroup label="Citrus">
13
+ <NativeSelectOption value="orange">Orange</NativeSelectOption>
14
+ <NativeSelectOption value="lemon">Lemon</NativeSelectOption>
15
+ </NativeSelectOptGroup>
16
+ <NativeSelectOptGroup label="Stone fruit">
17
+ <NativeSelectOption value="peach">Peach</NativeSelectOption>
18
+ <NativeSelectOption value="plum">Plum</NativeSelectOption>
19
+ </NativeSelectOptGroup>
20
+ </NativeSelect>
21
+ );
22
+
23
+ const meta = {
24
+ title: "Form Inputs/NativeSelect",
25
+ parameters: {
26
+ layout: "centered",
27
+ docs: { source: { code: "<NativeSelect defaultValue=\"\">\\n <NativeSelectOption value=\"\" disabled>Select a fruit…</NativeSelectOption>\\n <NativeSelectOptGroup label=\"Citrus\">\\n <NativeSelectOption value=\"orange\">Orange</NativeSelectOption>\\n </NativeSelectOptGroup>\\n</NativeSelect>", language: "tsx" } },
28
+ },
29
+ } satisfies Meta;
30
+
31
+ export default meta;
32
+
33
+ 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 /> };