@kinetixui/ui 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/index.d.ts +11 -1
  2. package/dist/index.js +395 -380
  3. package/package.json +2 -2
  4. package/src/components/accordion.tsx +1 -1
  5. package/src/components/alert-dialog.tsx +2 -2
  6. package/src/components/app-bar.tsx +1 -1
  7. package/src/components/audio-player.tsx +3 -0
  8. package/src/components/badge.tsx +13 -4
  9. package/src/components/button.tsx +2 -2
  10. package/src/components/calendar.tsx +9 -5
  11. package/src/components/checkbox.tsx +1 -1
  12. package/src/components/circular-progress.tsx +1 -1
  13. package/src/components/command.tsx +4 -2
  14. package/src/components/context-menu.tsx +2 -2
  15. package/src/components/dialog.tsx +3 -3
  16. package/src/components/drawer.tsx +2 -2
  17. package/src/components/dropdown-menu.tsx +4 -2
  18. package/src/components/fab.tsx +1 -1
  19. package/src/components/file-upload.tsx +1 -1
  20. package/src/components/hover-card.tsx +1 -1
  21. package/src/components/image.tsx +2 -2
  22. package/src/components/inform.tsx +1 -1
  23. package/src/components/input-group.tsx +2 -2
  24. package/src/components/input-otp.tsx +3 -3
  25. package/src/components/input.tsx +2 -2
  26. package/src/components/label.tsx +1 -1
  27. package/src/components/list.tsx +1 -1
  28. package/src/components/menubar.tsx +2 -2
  29. package/src/components/modal.tsx +2 -2
  30. package/src/components/navigation-menu.tsx +4 -4
  31. package/src/components/number-input.tsx +3 -3
  32. package/src/components/popover.tsx +1 -1
  33. package/src/components/radio-group.tsx +1 -1
  34. package/src/components/resizable.tsx +1 -1
  35. package/src/components/select.tsx +5 -3
  36. package/src/components/sheet.tsx +3 -3
  37. package/src/components/sidebar.tsx +5 -5
  38. package/src/components/slider.tsx +1 -1
  39. package/src/components/switch.tsx +1 -1
  40. package/src/components/tabs.tsx +1 -1
  41. package/src/components/tag.tsx +27 -15
  42. package/src/components/textarea.tsx +2 -2
  43. package/src/components/toggle.tsx +1 -1
  44. package/src/components/tooltip.tsx +1 -1
  45. package/src/components-behavior.test.tsx +37 -0
  46. package/tailwind.config.ts +29 -0
@@ -18,7 +18,7 @@ const SheetOverlay = React.forwardRef<
18
18
  <SheetPrimitive.Overlay
19
19
  ref={ref}
20
20
  className={cn(
21
- "fixed inset-0 z-50 bg-foreground/40 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
21
+ "fixed inset-0 z-overlay bg-foreground/40 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
22
22
  className,
23
23
  )}
24
24
  {...props}
@@ -27,7 +27,7 @@ const SheetOverlay = React.forwardRef<
27
27
  SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
28
28
 
29
29
  const sheetVariants = cva(
30
- "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 font-sans",
30
+ "fixed z-overlay gap-4 bg-background p-6 shadow-lg transition ease-standard data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-base data-[state=open]:duration-slow font-sans",
31
31
  {
32
32
  variants: {
33
33
  side: {
@@ -55,7 +55,7 @@ const SheetContent = React.forwardRef<
55
55
  <SheetOverlay />
56
56
  <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
57
57
  {children}
58
- <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring">
58
+ <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-muted transition-opacity hover:opacity-visible focus:outline-none focus:ring-2 focus:ring-ring">
59
59
  <X className="size-4" />
60
60
  <span className="sr-only">Close</span>
61
61
  </SheetPrimitive.Close>
@@ -159,7 +159,7 @@ const Sidebar = React.forwardRef<
159
159
  >
160
160
  <div
161
161
  className={cn(
162
- "relative h-svh w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear",
162
+ "relative h-svh w-[--sidebar-width] bg-transparent transition-[width] duration-fast ease-linear",
163
163
  "group-data-[collapsible=offcanvas]:w-0",
164
164
  variant === "floating" || variant === "inset"
165
165
  ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4))]"
@@ -168,7 +168,7 @@ const Sidebar = React.forwardRef<
168
168
  />
169
169
  <div
170
170
  className={cn(
171
- "fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
171
+ "fixed inset-y-0 z-docked hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-fast ease-linear md:flex",
172
172
  side === "left"
173
173
  ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
174
174
  : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
@@ -269,8 +269,8 @@ const SidebarGroupLabel = React.forwardRef<
269
269
  ref={ref}
270
270
  data-sidebar="group-label"
271
271
  className={cn(
272
- "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
273
- "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
272
+ "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none transition-[margin,opacity] duration-fast ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
273
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-hidden",
274
274
  className,
275
275
  )}
276
276
  {...props}
@@ -297,7 +297,7 @@ const SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<"li
297
297
  SidebarMenuItem.displayName = "SidebarMenuItem";
298
298
 
299
299
  const sidebarMenuButtonVariants = cva(
300
- "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
300
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-disabled group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-disabled data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
301
301
  {
302
302
  variants: {
303
303
  variant: {
@@ -16,7 +16,7 @@ const Slider = React.forwardRef<
16
16
  <SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-muted">
17
17
  <SliderPrimitive.Range className="absolute h-full bg-primary" />
18
18
  </SliderPrimitive.Track>
19
- <SliderPrimitive.Thumb className="block size-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
19
+ <SliderPrimitive.Thumb className="block size-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-disabled" />
20
20
  </SliderPrimitive.Root>
21
21
  ));
22
22
  Slider.displayName = SliderPrimitive.Root.displayName;
@@ -18,7 +18,7 @@ const Switch = React.forwardRef<
18
18
  className={cn(
19
19
  "peer inline-flex h-6 w-12 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors",
20
20
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
21
- "disabled:cursor-not-allowed disabled:opacity-50",
21
+ "disabled:cursor-not-allowed disabled:opacity-disabled",
22
22
  "data-[state=checked]:bg-primary data-[state=unchecked]:bg-tertiary",
23
23
  className,
24
24
  )}
@@ -30,7 +30,7 @@ const TabsTrigger = React.forwardRef<
30
30
  className={cn(
31
31
  "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all",
32
32
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
33
- "disabled:pointer-events-none disabled:opacity-50",
33
+ "disabled:pointer-events-none disabled:opacity-disabled",
34
34
  "data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
35
35
  className,
36
36
  )}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
+ import { Slot, Slottable } from "@radix-ui/react-slot";
4
5
  import { cva, type VariantProps } from "class-variance-authority";
5
6
  import { X } from "lucide-react";
6
7
  import { cn } from "../lib/utils";
@@ -32,24 +33,35 @@ export interface TagProps
32
33
  VariantProps<typeof tagVariants> {
33
34
  /** show a dismiss button; called when it's clicked */
34
35
  onRemove?: () => void;
36
+ /** render as the single child element (Radix Slot) instead of <span> —
37
+ * e.g. `<Tag asChild><a href="/filter">Active</a></Tag>` for a linked tag.
38
+ * Combined with `onRemove`, the remove button nests inside the slotted
39
+ * element (Slot can only render one root node) — fine for a <div>/<span>,
40
+ * but avoid pairing asChild+onRemove with an <a>, since a nested <button>
41
+ * inside an anchor is invalid HTML. */
42
+ asChild?: boolean;
35
43
  }
36
44
 
37
45
  const Tag = React.forwardRef<HTMLSpanElement, TagProps>(
38
- ({ className, variant, onRemove, children, ...props }, ref) => (
39
- <span ref={ref} className={cn(tagVariants({ variant }), className)} {...props}>
40
- {children}
41
- {onRemove && (
42
- <button
43
- type="button"
44
- onClick={onRemove}
45
- aria-label="Remove"
46
- className="-mr-0.5 ml-0.5 rounded-[2px] opacity-70 outline-none transition-opacity hover:opacity-100 focus-visible:ring-1 focus-visible:ring-current"
47
- >
48
- <X className="size-3.5" />
49
- </button>
50
- )}
51
- </span>
52
- ),
46
+ ({ className, variant, onRemove, children, asChild = false, ...props }, ref) => {
47
+ const Comp = asChild ? Slot : "span";
48
+ const removeButton = onRemove && (
49
+ <button
50
+ type="button"
51
+ onClick={onRemove}
52
+ aria-label="Remove"
53
+ className="-mr-0.5 ml-0.5 rounded-[2px] opacity-muted outline-none transition-opacity hover:opacity-visible focus-visible:ring-1 focus-visible:ring-current"
54
+ >
55
+ <X className="size-3.5" />
56
+ </button>
57
+ );
58
+ return (
59
+ <Comp ref={ref} className={cn(tagVariants({ variant }), className)} {...props}>
60
+ {asChild ? <Slottable>{children}</Slottable> : children}
61
+ {removeButton}
62
+ </Comp>
63
+ );
64
+ },
53
65
  );
54
66
  Tag.displayName = "Tag";
55
67
 
@@ -18,7 +18,7 @@ const textareaVariants = cva(
18
18
  "placeholder:text-muted-foreground",
19
19
  "outline-none transition-colors",
20
20
  "focus-visible:border-primary focus-visible:shadow-focus",
21
- "disabled:cursor-not-allowed disabled:opacity-50",
21
+ "disabled:cursor-not-allowed disabled:opacity-disabled",
22
22
  "aria-[invalid=true]:border-destructive aria-[invalid=true]:focus-visible:border-destructive aria-[invalid=true]:focus-visible:shadow-focus-destructive",
23
23
  ],
24
24
  {
@@ -27,7 +27,7 @@ const textareaVariants = cva(
27
27
  Default: "",
28
28
  Focus: "border-primary shadow-focus",
29
29
  Error: "border-destructive",
30
- Disabled: "opacity-50 pointer-events-none resize-none",
30
+ Disabled: "opacity-disabled pointer-events-none resize-none",
31
31
  },
32
32
  },
33
33
  defaultVariants: { state: "Default" },
@@ -6,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority";
6
6
  import { cn } from "../lib/utils";
7
7
 
8
8
  const toggleVariants = cva(
9
- "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium font-sans transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground data-[state=on]:ring-1 data-[state=on]:ring-inset data-[state=on]:ring-ring [&_svg]:size-4 [&_svg]:shrink-0",
9
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium font-sans transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-disabled data-[state=on]:bg-accent data-[state=on]:text-accent-foreground data-[state=on]:ring-1 data-[state=on]:ring-inset data-[state=on]:ring-ring [&_svg]:size-4 [&_svg]:shrink-0",
10
10
  {
11
11
  variants: {
12
12
  variant: {
@@ -17,7 +17,7 @@ const TooltipContent = React.forwardRef<
17
17
  ref={ref}
18
18
  sideOffset={sideOffset}
19
19
  className={cn(
20
- "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground font-sans",
20
+ "z-overlay overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground font-sans",
21
21
  "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
22
22
  "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
23
23
  className,
@@ -5,6 +5,7 @@ import { describe, expect, it, vi } from "vitest";
5
5
  import { Badge } from "./components/badge";
6
6
  import { Button } from "./components/button";
7
7
  import { Input } from "./components/input";
8
+ import { Tag } from "./components/tag";
8
9
  import {
9
10
  Accordion,
10
11
  AccordionContent,
@@ -55,6 +56,42 @@ describe("Badge", () => {
55
56
  expect(screen.getByText("tag").className).not.toBe(def);
56
57
  expect(screen.getByText("tag")).toHaveClass("text-secondary");
57
58
  });
59
+
60
+ it("renders as a child element with asChild", () => {
61
+ render(
62
+ <Badge asChild>
63
+ <a href="/new">New</a>
64
+ </Badge>,
65
+ );
66
+ const link = screen.getByRole("link", { name: "New" });
67
+ expect(link).toHaveAttribute("href", "/new");
68
+ expect(link).toHaveClass("bg-primary"); // badge classes merged onto the <a>
69
+ });
70
+ });
71
+
72
+ describe("Tag", () => {
73
+ it("renders as a child element with asChild", () => {
74
+ render(
75
+ <Tag asChild>
76
+ <a href="/filter">Active</a>
77
+ </Tag>,
78
+ );
79
+ const link = screen.getByRole("link", { name: "Active" });
80
+ expect(link).toHaveAttribute("href", "/filter");
81
+ expect(link).toHaveClass("bg-accent"); // tag classes merged onto the <a>
82
+ });
83
+
84
+ it("nests the remove button inside the slotted element when combined with onRemove", async () => {
85
+ const onRemove = vi.fn();
86
+ render(
87
+ <Tag asChild onRemove={onRemove}>
88
+ <div>Active</div>
89
+ </Tag>,
90
+ );
91
+ const remove = screen.getByRole("button", { name: "Remove" });
92
+ await userEvent.click(remove);
93
+ expect(onRemove).toHaveBeenCalledOnce();
94
+ });
58
95
  });
59
96
 
60
97
  describe("Accordion", () => {
@@ -94,6 +94,35 @@ export default {
94
94
  "2xl": "calc(var(--radius-lg) + 8px)",
95
95
  full: "var(--radius-full)",
96
96
  },
97
+ // Motion + scale — tokens/primitives/{motion,opacity,z-index}.json.
98
+ // Added alongside Tailwind's own numeric scales (z-10, opacity-50, …),
99
+ // not replacing them — these are the token-backed semantic names.
100
+ transitionDuration: {
101
+ fast: "var(--duration-fast)",
102
+ base: "var(--duration-base)",
103
+ slow: "var(--duration-slow)",
104
+ slower: "var(--duration-slower)",
105
+ },
106
+ transitionTimingFunction: {
107
+ // overrides Tailwind's built-in `ease-linear` with the identical
108
+ // cubic-bezier(0,0,1,1) value, now token-backed instead of hardcoded
109
+ linear: "var(--easing-linear)",
110
+ standard: "var(--easing-standard)",
111
+ },
112
+ zIndex: {
113
+ base: "var(--z-index-base)",
114
+ raised: "var(--z-index-raised)",
115
+ docked: "var(--z-index-docked)",
116
+ focus: "var(--z-index-focus)",
117
+ sticky: "var(--z-index-sticky)",
118
+ overlay: "var(--z-index-overlay)",
119
+ },
120
+ opacity: {
121
+ hidden: "var(--opacity-hidden)",
122
+ disabled: "var(--opacity-disabled)",
123
+ muted: "var(--opacity-muted)",
124
+ visible: "var(--opacity-visible)",
125
+ },
97
126
  keyframes: {
98
127
  "accordion-down": { from: { height: "0" }, to: { height: "var(--radix-accordion-content-height)" } },
99
128
  "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" } },