@kinetixui/ui 0.16.1 → 0.17.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
@@ -86,7 +86,7 @@ declare const Button: React$1.ForwardRefExoticComponent<ButtonProps & React$1.Re
86
86
  */
87
87
  declare const inputVariants: (props?: ({
88
88
  state?: "Default" | "Focus" | "Disabled" | "Error" | null | undefined;
89
- corners?: "sharp" | "default" | "pill" | "rounded" | null | undefined;
89
+ corners?: "rounded" | "sharp" | "default" | "pill" | null | undefined;
90
90
  } & class_variance_authority_types.ClassProp) | undefined) => string;
91
91
  interface InputProps extends Omit<React$1.InputHTMLAttributes<HTMLInputElement>, "size">, VariantProps<typeof inputVariants> {
92
92
  }
@@ -337,7 +337,7 @@ declare const SheetClose: React$1.ForwardRefExoticComponent<DialogPrimitive.Dial
337
337
  declare const SheetPortal: React$1.FC<DialogPrimitive.DialogPortalProps>;
338
338
  declare const SheetOverlay: React$1.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogOverlayProps & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
339
339
  declare const sheetVariants: (props?: ({
340
- side?: "top" | "right" | "bottom" | "left" | null | undefined;
340
+ side?: "bottom" | "left" | "right" | "top" | null | undefined;
341
341
  } & class_variance_authority_types.ClassProp) | undefined) => string;
342
342
  interface SheetContentProps extends React$1.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>, VariantProps<typeof sheetVariants> {
343
343
  }
@@ -1303,7 +1303,7 @@ declare const ButtonGroupText: React$1.ForwardRefExoticComponent<React$1.HTMLAtt
1303
1303
  */
1304
1304
  declare const nativeSelectVariants: (props?: ({
1305
1305
  state?: "Default" | "Focus" | "Disabled" | "Error" | null | undefined;
1306
- corners?: "sharp" | "default" | "pill" | "rounded" | null | undefined;
1306
+ corners?: "rounded" | "sharp" | "default" | "pill" | null | undefined;
1307
1307
  } & class_variance_authority_types.ClassProp) | undefined) => string;
1308
1308
  interface NativeSelectProps extends Omit<React$1.SelectHTMLAttributes<HTMLSelectElement>, "size">, VariantProps<typeof nativeSelectVariants> {
1309
1309
  }
@@ -1617,7 +1617,7 @@ interface DataGridColumn<TData> {
1617
1617
  /** fixed to this side, not draggable, and excluded from the reorder sequence */
1618
1618
  pinned?: "left" | "right";
1619
1619
  sortable?: boolean;
1620
- /** double-click swaps the cell for a text input; commit fires on Enter/blur, Escape cancels */
1620
+ /** double-click — or Enter / F2 on the focused cell — swaps it for a text input; commit fires on Enter/blur, Escape cancels */
1621
1621
  editable?: boolean;
1622
1622
  onCellEdit?: (row: TData, rowIndex: number, value: string) => void;
1623
1623
  }
package/dist/index.js CHANGED
@@ -7,7 +7,35 @@ import { cva } from "class-variance-authority";
7
7
 
8
8
  // src/lib/utils.ts
9
9
  import { clsx } from "clsx";
10
- import { twMerge } from "tailwind-merge";
10
+ import { extendTailwindMerge } from "tailwind-merge";
11
+ var twMerge = extendTailwindMerge({
12
+ extend: {
13
+ classGroups: {
14
+ "font-size": [
15
+ {
16
+ text: [
17
+ "display-lg",
18
+ "display-md",
19
+ "display-sm",
20
+ "headline-lg",
21
+ "headline-md",
22
+ "headline-sm",
23
+ "title-lg",
24
+ "title-md",
25
+ "title-sm",
26
+ "title-dialog",
27
+ "label-lg",
28
+ "label-md",
29
+ "label-sm",
30
+ "body-lg",
31
+ "body-md",
32
+ "body-sm"
33
+ ]
34
+ }
35
+ ]
36
+ }
37
+ }
38
+ });
11
39
  function cn(...inputs) {
12
40
  return twMerge(clsx(inputs));
13
41
  }
@@ -28,12 +56,16 @@ var buttonVariants = cva(
28
56
  variant: {
29
57
  // bg primary / text on-primary; hover+active step down the blue ramp; disabled -> border grey
30
58
  // rest = --primary; hover/active dim it via opacity so it works in BOTH themes.
59
+ // Alphas are bounded by contrast, not taste: text on the dimmed fill must stay >= 4.5:1
60
+ // (light: 90% = 5.05, 85% = 4.54, 80% = 4.11 fails) — enforced by check:contrast.
31
61
  // (the old hover:bg-[--color-blue-600] was a hard-coded dark navy — on dark mode,
32
62
  // where --primary is light, it inverted the button into the near-black background.)
33
- Primary: "bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80 disabled:bg-border disabled:text-muted-foreground",
63
+ Primary: "bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/85 disabled:bg-border disabled:text-muted-foreground",
34
64
  // rest = --secondary; hover/active promote to the --secondary-foreground fill with
35
- // inverted text — theme-aware, unlike the previous hard-coded green primitives
36
- Secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-foreground hover:text-secondary active:bg-secondary-foreground/90 active:text-secondary focus-visible:bg-secondary disabled:bg-border disabled:text-muted-foreground",
65
+ // inverted text — theme-aware, unlike the previous hard-coded green primitives.
66
+ // Active is solid like hover: dimming it (90% was 3.97:1) drops the text under AA,
67
+ // and anything above ~97% is imperceptible. Enforced by check:contrast.
68
+ Secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-foreground hover:text-secondary active:bg-secondary-foreground active:text-secondary focus-visible:bg-secondary disabled:bg-border disabled:text-muted-foreground",
37
69
  // 1px outline; hover fills with accent + strengthens the edge to --ring so the
38
70
  // state stays visible on the darkest surfaces (WCAG 2.2 SC 1.4.11); active pins primary
39
71
  Outline: "border border-input bg-transparent text-foreground hover:border-ring hover:bg-accent active:border-primary active:bg-accent active:text-primary disabled:border-input disabled:text-muted-foreground disabled:bg-transparent",
@@ -45,11 +77,11 @@ var buttonVariants = cva(
45
77
  },
46
78
  size: {
47
79
  // padding = Figma spacing/3 + spacing/2 ; type = Label Small (11/16, +0.5 tracking)
48
- sm: "px-3 py-2 text-[11px] leading-4 tracking-[0.5px]",
80
+ sm: "px-3 py-2 text-label-sm",
49
81
  // spacing/4 + spacing/3 ; Label Medium (12/16, +0.5)
50
- md: "px-4 py-3 text-[12px] leading-4 tracking-[0.5px]",
82
+ md: "px-4 py-3 text-label-md",
51
83
  // spacing/6 + spacing/3 ; Label Large (14/20, +0.1)
52
- lg: "px-6 py-3 text-[14px] leading-5 tracking-[0.1px]",
84
+ lg: "px-6 py-3 text-label-lg",
53
85
  // spacing/3 all round, square
54
86
  icon: "p-3 [&>*:not(svg)]:sr-only"
55
87
  },
@@ -71,9 +103,9 @@ var buttonVariants = cva(
71
103
  compoundVariants: [
72
104
  { variant: "Destructive", class: "focus-visible:shadow-focus-destructive" },
73
105
  { variant: "Primary", state: "Hover", class: "bg-primary/90" },
74
- { variant: "Primary", state: "Active", class: "bg-primary/80" },
106
+ { variant: "Primary", state: "Active", class: "bg-primary/85" },
75
107
  { variant: "Secondary", state: "Hover", class: "bg-secondary-foreground text-secondary" },
76
- { variant: "Secondary", state: "Active", class: "bg-secondary-foreground/90 text-secondary" },
108
+ { variant: "Secondary", state: "Active", class: "bg-secondary-foreground text-secondary" },
77
109
  { variant: "Outline", state: "Hover", class: "border-ring bg-accent" },
78
110
  { variant: "Outline", state: "Active", class: "border-primary bg-accent text-primary" },
79
111
  { variant: "Ghost", state: "Hover", class: "bg-accent ring-1 ring-inset ring-ring" },
@@ -110,7 +142,7 @@ import { jsx as jsx2 } from "react/jsx-runtime";
110
142
  var inputVariants = cva2(
111
143
  [
112
144
  "flex w-full border border-input bg-background px-3 py-3",
113
- "font-sans text-[14px] leading-5 tracking-[0.25px] text-foreground",
145
+ "font-sans text-body-md text-foreground",
114
146
  "placeholder:text-muted-foreground",
115
147
  "outline-none transition-colors",
116
148
  "focus-visible:border-primary focus-visible:shadow-focus",
@@ -160,7 +192,7 @@ import { jsx as jsx3 } from "react/jsx-runtime";
160
192
  var textareaVariants = cva3(
161
193
  [
162
194
  "flex w-full min-h-[100px] resize-y rounded-sm border border-input bg-background px-3 py-3",
163
- "font-sans text-[14px] leading-5 tracking-[0.25px] text-foreground",
195
+ "font-sans text-body-md text-foreground",
164
196
  "placeholder:text-muted-foreground",
165
197
  "outline-none transition-colors",
166
198
  "focus-visible:border-primary focus-visible:shadow-focus",
@@ -393,7 +425,7 @@ import { Slot as Slot2 } from "@radix-ui/react-slot";
393
425
  import { cva as cva5 } from "class-variance-authority";
394
426
  import { jsx as jsx9 } from "react/jsx-runtime";
395
427
  var badgeVariants = cva5(
396
- "inline-flex items-center rounded-full px-2.5 py-1 text-xs font-medium font-sans tracking-[0.5px] transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
428
+ "inline-flex items-center rounded-full px-2.5 py-1 text-label-md font-sans transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
397
429
  {
398
430
  variants: {
399
431
  variant: {
@@ -856,7 +888,7 @@ var SelectTrigger = React21.forwardRef(({ className, children, ...props }, ref)
856
888
  ref,
857
889
  className: cn(
858
890
  "flex min-h-[44px] w-full items-center justify-between rounded-sm border border-input bg-background px-3 py-3",
859
- "font-sans text-[14px] leading-5 tracking-[0.25px] text-foreground",
891
+ "font-sans text-body-md text-foreground",
860
892
  "data-[placeholder]:text-muted-foreground [&>span]:line-clamp-1",
861
893
  "outline-none transition-colors",
862
894
  "focus:border-primary focus:shadow-focus",
@@ -1041,7 +1073,7 @@ function Modal({
1041
1073
  ),
1042
1074
  children: [
1043
1075
  /* @__PURE__ */ jsxs11("header", { className: "flex items-center justify-between px-6 pb-3 pt-5", children: [
1044
- /* @__PURE__ */ jsx25(DialogPrimitive2.Title, { className: "min-w-0 flex-1 text-[18px] font-semibold leading-6 text-foreground", children: title }),
1076
+ /* @__PURE__ */ jsx25(DialogPrimitive2.Title, { className: "min-w-0 flex-1 text-title-dialog text-foreground", children: title }),
1045
1077
  /* @__PURE__ */ jsx25(
1046
1078
  DialogPrimitive2.Close,
1047
1079
  {
@@ -1053,7 +1085,7 @@ function Modal({
1053
1085
  ] }),
1054
1086
  /* @__PURE__ */ jsx25("div", { className: "h-px w-full bg-border" }),
1055
1087
  /* @__PURE__ */ jsxs11("div", { className: "px-6 py-5", children: [
1056
- description && /* @__PURE__ */ jsx25(DialogPrimitive2.Description, { className: "text-[14px] leading-5 tracking-[0.25px] text-muted-foreground", children: description }),
1088
+ description && /* @__PURE__ */ jsx25(DialogPrimitive2.Description, { className: "text-body-md text-muted-foreground", children: description }),
1057
1089
  children
1058
1090
  ] }),
1059
1091
  /* @__PURE__ */ jsx25("div", { className: "h-px w-full bg-border" }),
@@ -1086,7 +1118,7 @@ function Skeleton({ className, ...props }) {
1086
1118
  import * as React24 from "react";
1087
1119
  import * as SliderPrimitive from "@radix-ui/react-slider";
1088
1120
  import { jsx as jsx27, jsxs as jsxs12 } from "react/jsx-runtime";
1089
- var Slider = React24.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxs12(
1121
+ var Slider = React24.forwardRef(({ className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }, ref) => /* @__PURE__ */ jsxs12(
1090
1122
  SliderPrimitive.Root,
1091
1123
  {
1092
1124
  ref,
@@ -1094,7 +1126,14 @@ var Slider = React24.forwardRef(({ className, ...props }, ref) => /* @__PURE__ *
1094
1126
  ...props,
1095
1127
  children: [
1096
1128
  /* @__PURE__ */ jsx27(SliderPrimitive.Track, { className: "relative h-1.5 w-full grow overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx27(SliderPrimitive.Range, { className: "absolute h-full bg-primary" }) }),
1097
- /* @__PURE__ */ jsx27(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" })
1129
+ /* @__PURE__ */ jsx27(
1130
+ SliderPrimitive.Thumb,
1131
+ {
1132
+ "aria-label": ariaLabel,
1133
+ "aria-labelledby": ariaLabelledBy,
1134
+ 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"
1135
+ }
1136
+ )
1098
1137
  ]
1099
1138
  }
1100
1139
  ));
@@ -1158,7 +1197,7 @@ import { cva as cva8 } from "class-variance-authority";
1158
1197
  import { X as X4 } from "lucide-react";
1159
1198
  import { jsx as jsx30, jsxs as jsxs13 } from "react/jsx-runtime";
1160
1199
  var tagVariants = cva8(
1161
- "inline-flex items-center gap-1 rounded-sm px-2 py-1 text-xs font-medium font-sans tracking-[0.5px]",
1200
+ "inline-flex items-center gap-1 rounded-sm px-2 py-1 text-label-md font-sans",
1162
1201
  {
1163
1202
  variants: {
1164
1203
  variant: {
@@ -2984,7 +3023,7 @@ var informVariants = cva14(
2984
3023
  {
2985
3024
  variants: {
2986
3025
  variant: {
2987
- information: "bg-info/10 text-info",
3026
+ information: "bg-info/10 text-info-on-container",
2988
3027
  warning: "bg-warning/15 text-warning",
2989
3028
  success: "bg-success/15 text-success",
2990
3029
  error: "bg-destructive/10 text-destructive",
@@ -3890,7 +3929,7 @@ var Kbd = React66.forwardRef(({ className, asChild = false, ...props }, ref) =>
3890
3929
  {
3891
3930
  ref,
3892
3931
  className: cn(
3893
- "inline-flex h-5 min-w-5 items-center justify-center rounded-sm border border-input bg-muted px-1.5 font-sans text-[11px] font-medium text-muted-foreground",
3932
+ "inline-flex h-5 min-w-5 items-center justify-center rounded-sm border border-input bg-muted px-1.5 font-sans text-label-sm text-muted-foreground",
3894
3933
  className
3895
3934
  ),
3896
3935
  ...props
@@ -4003,7 +4042,7 @@ import { jsx as jsx75, jsxs as jsxs41 } from "react/jsx-runtime";
4003
4042
  var nativeSelectVariants = cva19(
4004
4043
  [
4005
4044
  "flex w-full appearance-none border border-input bg-background px-3 py-3 pe-9",
4006
- "font-sans text-[14px] leading-5 tracking-[0.25px] text-foreground",
4045
+ "font-sans text-body-md text-foreground",
4007
4046
  "outline-none transition-colors",
4008
4047
  "focus-visible:border-primary focus-visible:shadow-focus",
4009
4048
  "disabled:cursor-not-allowed disabled:opacity-disabled",
@@ -4104,7 +4143,7 @@ var bannerVariants = cva20(
4104
4143
  {
4105
4144
  variants: {
4106
4145
  variant: {
4107
- information: "border-info/20 bg-info/10 text-info",
4146
+ information: "border-info/20 bg-info/10 text-info-on-container",
4108
4147
  warning: "border-warning/25 bg-warning/15 text-warning",
4109
4148
  success: "border-success/25 bg-success/15 text-success",
4110
4149
  error: "border-destructive/20 bg-destructive/10 text-destructive",
@@ -4627,6 +4666,12 @@ var MultiSelect = React79.forwardRef(
4627
4666
  const current = value ?? internal;
4628
4667
  const [open, setOpen] = React79.useState(false);
4629
4668
  const [query, setQuery] = React79.useState("");
4669
+ const anchorRef = React79.useRef(null);
4670
+ const setRefs = (node) => {
4671
+ anchorRef.current = node;
4672
+ if (typeof ref === "function") ref(node);
4673
+ else if (ref) ref.current = node;
4674
+ };
4630
4675
  const set = (next) => {
4631
4676
  setInternal(next);
4632
4677
  onValueChange?.(next);
@@ -4641,7 +4686,7 @@ var MultiSelect = React79.forwardRef(
4641
4686
  /* @__PURE__ */ jsx85(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs50(
4642
4687
  "div",
4643
4688
  {
4644
- ref,
4689
+ ref: setRefs,
4645
4690
  role: "combobox",
4646
4691
  "aria-expanded": open,
4647
4692
  "aria-haspopup": "listbox",
@@ -4671,7 +4716,10 @@ var MultiSelect = React79.forwardRef(
4671
4716
  {
4672
4717
  align: "start",
4673
4718
  className: "w-[var(--radix-popover-trigger-width)] p-0",
4674
- onOpenAutoFocus: (e) => e.preventDefault(),
4719
+ onCloseAutoFocus: (e) => {
4720
+ e.preventDefault();
4721
+ anchorRef.current?.focus();
4722
+ },
4675
4723
  children: /* @__PURE__ */ jsxs50(Command, { shouldFilter: !creatable, children: [
4676
4724
  /* @__PURE__ */ jsx85(CommandInput, { placeholder: "Search\u2026", value: query, onValueChange: setQuery }),
4677
4725
  /* @__PURE__ */ jsxs50(CommandList, { children: [
@@ -4778,6 +4826,36 @@ var Tour = ({
4778
4826
  const [rect, setRect] = React81.useState(null);
4779
4827
  const step = steps[stepIndex];
4780
4828
  React81.useEffect(() => setMounted(true), []);
4829
+ const titleId = React81.useId();
4830
+ const dialogRef = React81.useRef(null);
4831
+ const returnFocusRef = React81.useRef(null);
4832
+ React81.useEffect(() => {
4833
+ if (!open) return;
4834
+ returnFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
4835
+ return () => returnFocusRef.current?.focus();
4836
+ }, [open]);
4837
+ React81.useEffect(() => {
4838
+ if (open && mounted) dialogRef.current?.focus();
4839
+ }, [open, mounted, stepIndex]);
4840
+ const trapTab = (e) => {
4841
+ if (e.key !== "Tab") return;
4842
+ const dialog = e.currentTarget;
4843
+ const focusable = Array.from(dialog.querySelectorAll("button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex='-1'])"));
4844
+ if (focusable.length === 0) {
4845
+ e.preventDefault();
4846
+ return;
4847
+ }
4848
+ const first = focusable[0];
4849
+ const last = focusable[focusable.length - 1];
4850
+ const active = document.activeElement;
4851
+ if (e.shiftKey && (active === first || active === dialog)) {
4852
+ e.preventDefault();
4853
+ last.focus();
4854
+ } else if (!e.shiftKey && active === last) {
4855
+ e.preventDefault();
4856
+ first.focus();
4857
+ }
4858
+ };
4781
4859
  React81.useEffect(() => {
4782
4860
  if (!open || !step) return;
4783
4861
  const update = () => {
@@ -4832,12 +4910,16 @@ var Tour = ({
4832
4910
  /* @__PURE__ */ jsxs52(
4833
4911
  "div",
4834
4912
  {
4913
+ ref: dialogRef,
4835
4914
  role: "dialog",
4836
4915
  "aria-modal": "true",
4837
- className: "fixed z-overlay w-80 rounded-lg border border-border bg-popover p-4 font-sans text-popover-foreground shadow-lg transition-all duration-fast",
4916
+ "aria-labelledby": titleId,
4917
+ tabIndex: -1,
4918
+ onKeyDown: trapTab,
4919
+ className: "fixed z-overlay w-80 rounded-lg border border-border bg-popover p-4 font-sans text-popover-foreground shadow-lg outline-none transition-all duration-fast",
4838
4920
  style: cardStyle,
4839
4921
  children: [
4840
- /* @__PURE__ */ jsx87("p", { className: "text-title-sm font-medium text-foreground", children: step.title }),
4922
+ /* @__PURE__ */ jsx87("p", { id: titleId, className: "text-title-sm font-medium text-foreground", children: step.title }),
4841
4923
  /* @__PURE__ */ jsx87("div", { className: "mt-1.5 text-body-sm text-muted-foreground", children: step.content }),
4842
4924
  /* @__PURE__ */ jsxs52("div", { className: "mt-4 flex items-center justify-between", children: [
4843
4925
  /* @__PURE__ */ jsxs52("span", { className: "text-label-sm text-muted-foreground", children: [
@@ -5032,10 +5114,21 @@ function DataGrid({
5032
5114
  window.addEventListener("pointermove", handleMove);
5033
5115
  window.addEventListener("pointerup", handleUp);
5034
5116
  }
5035
- function commitEdit(column, row, rowIndex, value) {
5036
- column.onCellEdit?.(row, rowIndex, value);
5117
+ const restoreFocusRef = React83.useRef(null);
5118
+ React83.useEffect(() => {
5119
+ const target = restoreFocusRef.current;
5120
+ if (editing || !target) return;
5121
+ restoreFocusRef.current = null;
5122
+ containerRef.current?.querySelector(`[data-cell="${target.rowIndex}:${target.columnId}"]`)?.focus();
5123
+ }, [editing]);
5124
+ function endEdit(restoreFocus) {
5125
+ if (restoreFocus && editing) restoreFocusRef.current = { rowIndex: editing.rowIndex, columnId: editing.columnId };
5037
5126
  setEditing(null);
5038
5127
  }
5128
+ function commitEdit(column, row, rowIndex, value, restoreFocus = false) {
5129
+ column.onCellEdit?.(row, rowIndex, value);
5130
+ endEdit(restoreFocus);
5131
+ }
5039
5132
  return /* @__PURE__ */ jsx89(
5040
5133
  "div",
5041
5134
  {
@@ -5074,9 +5167,16 @@ function DataGrid({
5074
5167
  });
5075
5168
  },
5076
5169
  onClick: () => column.sortable && toggleSort(column.id),
5170
+ tabIndex: column.sortable ? 0 : void 0,
5171
+ onKeyDown: (e) => {
5172
+ if (column.sortable && e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) {
5173
+ e.preventDefault();
5174
+ toggleSort(column.id);
5175
+ }
5176
+ },
5077
5177
  className: cn(
5078
5178
  "relative flex h-10 shrink-0 select-none items-center gap-1 px-2 font-medium text-muted-foreground",
5079
- column.sortable && "cursor-pointer hover:text-foreground",
5179
+ column.sortable && "cursor-pointer outline-none hover:text-foreground focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring",
5080
5180
  column.pinned && "bg-background"
5081
5181
  ),
5082
5182
  style: { width, ...pinnedStyle },
@@ -5114,8 +5214,20 @@ function DataGrid({
5114
5214
  "div",
5115
5215
  {
5116
5216
  role: "gridcell",
5217
+ "data-cell": `${rowIndex}:${column.id}`,
5117
5218
  onDoubleClick: () => column.editable && setEditing({ rowIndex, columnId: column.id }),
5118
- className: cn("flex shrink-0 items-center px-2", column.pinned && "bg-background"),
5219
+ tabIndex: column.editable ? 0 : void 0,
5220
+ onKeyDown: (e) => {
5221
+ if (column.editable && e.target === e.currentTarget && (e.key === "Enter" || e.key === "F2")) {
5222
+ e.preventDefault();
5223
+ setEditing({ rowIndex, columnId: column.id });
5224
+ }
5225
+ },
5226
+ className: cn(
5227
+ "flex shrink-0 items-center px-2",
5228
+ column.editable && "outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring",
5229
+ column.pinned && "bg-background"
5230
+ ),
5119
5231
  style: { width, ...pinnedStyle },
5120
5232
  children: isEditing ? /* @__PURE__ */ jsx89(
5121
5233
  "input",
@@ -5124,8 +5236,8 @@ function DataGrid({
5124
5236
  defaultValue: column.value ? String(column.value(row)) : "",
5125
5237
  className: "h-7 w-full rounded-sm border-0 bg-transparent px-1 text-sm outline-none ring-1 ring-inset ring-ring",
5126
5238
  onKeyDown: (e) => {
5127
- if (e.key === "Enter") commitEdit(column, row, rowIndex, e.currentTarget.value);
5128
- else if (e.key === "Escape") setEditing(null);
5239
+ if (e.key === "Enter") commitEdit(column, row, rowIndex, e.currentTarget.value, true);
5240
+ else if (e.key === "Escape") endEdit(true);
5129
5241
  },
5130
5242
  onBlur: (e) => commitEdit(column, row, rowIndex, e.currentTarget.value)
5131
5243
  }
@@ -5540,7 +5652,6 @@ var ColorPicker = React86.forwardRef(
5540
5652
  max: 360,
5541
5653
  step: 1,
5542
5654
  onValueChange: ([nh]) => commit(nh ?? h, s, v, a),
5543
- "aria-label": "Hue",
5544
5655
  className: "relative flex h-4 w-full touch-none select-none items-center",
5545
5656
  children: [
5546
5657
  /* @__PURE__ */ jsx92(
@@ -5552,7 +5663,7 @@ var ColorPicker = React86.forwardRef(
5552
5663
  }
5553
5664
  }
5554
5665
  ),
5555
- /* @__PURE__ */ jsx92(SliderPrimitive3.Thumb, { className: "block size-4 rounded-full border-2 border-white bg-background shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" })
5666
+ /* @__PURE__ */ jsx92(SliderPrimitive3.Thumb, { "aria-label": "Hue", className: "block size-4 rounded-full border-2 border-white bg-background shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" })
5556
5667
  ]
5557
5668
  }
5558
5669
  ),
@@ -5564,7 +5675,6 @@ var ColorPicker = React86.forwardRef(
5564
5675
  max: 100,
5565
5676
  step: 1,
5566
5677
  onValueChange: ([na]) => commit(h, s, v, (na ?? a * 100) / 100),
5567
- "aria-label": "Alpha",
5568
5678
  className: "relative flex h-4 w-full touch-none select-none items-center",
5569
5679
  children: [
5570
5680
  /* @__PURE__ */ jsx92(
@@ -5584,7 +5694,7 @@ var ColorPicker = React86.forwardRef(
5584
5694
  )
5585
5695
  }
5586
5696
  ),
5587
- /* @__PURE__ */ jsx92(SliderPrimitive3.Thumb, { className: "block size-4 rounded-full border-2 border-white bg-background shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" })
5697
+ /* @__PURE__ */ jsx92(SliderPrimitive3.Thumb, { "aria-label": "Alpha", className: "block size-4 rounded-full border-2 border-white bg-background shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" })
5588
5698
  ]
5589
5699
  }
5590
5700
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kinetixui/ui",
3
- "version": "0.16.1",
3
+ "version": "0.17.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": [
@@ -44,7 +44,7 @@
44
44
  "@dnd-kit/sortable": "^10.0.0",
45
45
  "@dnd-kit/utilities": "^3.2.2",
46
46
  "@hookform/resolvers": "^5.9.1",
47
- "@kinetixui/tokens": "0.16.1",
47
+ "@kinetixui/tokens": "0.17.0",
48
48
  "@radix-ui/react-accordion": "^1.2.20",
49
49
  "@radix-ui/react-alert-dialog": "^1.1.23",
50
50
  "@radix-ui/react-aspect-ratio": "^1.1.15",
@@ -101,6 +101,7 @@
101
101
  "@types/react": "^19.3.0",
102
102
  "@types/react-dom": "^19.3.0",
103
103
  "@vitejs/plugin-react": "^6.1.1",
104
+ "axe-core": "4.13.0",
104
105
  "jsdom": "^30.0.1",
105
106
  "react": "^19.3.0",
106
107
  "react-dom": "^19.3.0",
@@ -15,7 +15,7 @@ import { cn } from "../lib/utils";
15
15
  * container. Hence the swapped bg/text on that one variant.
16
16
  */
17
17
  const badgeVariants = cva(
18
- "inline-flex items-center rounded-full px-2.5 py-1 text-xs font-medium font-sans tracking-[0.5px] transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
18
+ "inline-flex items-center rounded-full px-2.5 py-1 text-label-md font-sans transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
19
19
  {
20
20
  variants: {
21
21
  variant: {
@@ -20,7 +20,7 @@ const bannerVariants = cva(
20
20
  {
21
21
  variants: {
22
22
  variant: {
23
- information: "border-info/20 bg-info/10 text-info",
23
+ information: "border-info/20 bg-info/10 text-info-on-container",
24
24
  warning: "border-warning/25 bg-warning/15 text-warning",
25
25
  success: "border-success/25 bg-success/15 text-success",
26
26
  error: "border-destructive/20 bg-destructive/10 text-destructive",
@@ -36,14 +36,18 @@ const buttonVariants = cva(
36
36
  variant: {
37
37
  // bg primary / text on-primary; hover+active step down the blue ramp; disabled -> border grey
38
38
  // rest = --primary; hover/active dim it via opacity so it works in BOTH themes.
39
+ // Alphas are bounded by contrast, not taste: text on the dimmed fill must stay >= 4.5:1
40
+ // (light: 90% = 5.05, 85% = 4.54, 80% = 4.11 fails) — enforced by check:contrast.
39
41
  // (the old hover:bg-[--color-blue-600] was a hard-coded dark navy — on dark mode,
40
42
  // where --primary is light, it inverted the button into the near-black background.)
41
43
  Primary:
42
- "bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80 disabled:bg-border disabled:text-muted-foreground",
44
+ "bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/85 disabled:bg-border disabled:text-muted-foreground",
43
45
  // rest = --secondary; hover/active promote to the --secondary-foreground fill with
44
- // inverted text — theme-aware, unlike the previous hard-coded green primitives
46
+ // inverted text — theme-aware, unlike the previous hard-coded green primitives.
47
+ // Active is solid like hover: dimming it (90% was 3.97:1) drops the text under AA,
48
+ // and anything above ~97% is imperceptible. Enforced by check:contrast.
45
49
  Secondary:
46
- "bg-secondary text-secondary-foreground hover:bg-secondary-foreground hover:text-secondary active:bg-secondary-foreground/90 active:text-secondary focus-visible:bg-secondary disabled:bg-border disabled:text-muted-foreground",
50
+ "bg-secondary text-secondary-foreground hover:bg-secondary-foreground hover:text-secondary active:bg-secondary-foreground active:text-secondary focus-visible:bg-secondary disabled:bg-border disabled:text-muted-foreground",
47
51
  // 1px outline; hover fills with accent + strengthens the edge to --ring so the
48
52
  // state stays visible on the darkest surfaces (WCAG 2.2 SC 1.4.11); active pins primary
49
53
  Outline:
@@ -58,11 +62,11 @@ const buttonVariants = cva(
58
62
  },
59
63
  size: {
60
64
  // padding = Figma spacing/3 + spacing/2 ; type = Label Small (11/16, +0.5 tracking)
61
- sm: "px-3 py-2 text-[11px] leading-4 tracking-[0.5px]",
65
+ sm: "px-3 py-2 text-label-sm",
62
66
  // spacing/4 + spacing/3 ; Label Medium (12/16, +0.5)
63
- md: "px-4 py-3 text-[12px] leading-4 tracking-[0.5px]",
67
+ md: "px-4 py-3 text-label-md",
64
68
  // spacing/6 + spacing/3 ; Label Large (14/20, +0.1)
65
- lg: "px-6 py-3 text-[14px] leading-5 tracking-[0.1px]",
69
+ lg: "px-6 py-3 text-label-lg",
66
70
  // spacing/3 all round, square
67
71
  icon: "p-3 [&>*:not(svg)]:sr-only",
68
72
  },
@@ -84,9 +88,9 @@ const buttonVariants = cva(
84
88
  compoundVariants: [
85
89
  { variant: "Destructive", class: "focus-visible:shadow-focus-destructive" },
86
90
  { variant: "Primary", state: "Hover", class: "bg-primary/90" },
87
- { variant: "Primary", state: "Active", class: "bg-primary/80" },
91
+ { variant: "Primary", state: "Active", class: "bg-primary/85" },
88
92
  { variant: "Secondary", state: "Hover", class: "bg-secondary-foreground text-secondary" },
89
- { variant: "Secondary", state: "Active", class: "bg-secondary-foreground/90 text-secondary" },
93
+ { variant: "Secondary", state: "Active", class: "bg-secondary-foreground text-secondary" },
90
94
  { variant: "Outline", state: "Hover", class: "border-ring bg-accent" },
91
95
  { variant: "Outline", state: "Active", class: "border-primary bg-accent text-primary" },
92
96
  { variant: "Ghost", state: "Hover", class: "bg-accent ring-1 ring-inset ring-ring" },
@@ -220,7 +220,6 @@ const ColorPicker = React.forwardRef<HTMLDivElement, ColorPickerProps>(
220
220
  max={360}
221
221
  step={1}
222
222
  onValueChange={([nh]) => commit(nh ?? h, s, v, a)}
223
- aria-label="Hue"
224
223
  className="relative flex h-4 w-full touch-none select-none items-center"
225
224
  >
226
225
  <SliderPrimitive.Track
@@ -230,7 +229,7 @@ const ColorPicker = React.forwardRef<HTMLDivElement, ColorPickerProps>(
230
229
  "linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))",
231
230
  }}
232
231
  />
233
- <SliderPrimitive.Thumb className="block size-4 rounded-full border-2 border-white bg-background shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" />
232
+ <SliderPrimitive.Thumb aria-label="Hue" className="block size-4 rounded-full border-2 border-white bg-background shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" />
234
233
  </SliderPrimitive.Root>
235
234
 
236
235
  {alpha && (
@@ -240,7 +239,6 @@ const ColorPicker = React.forwardRef<HTMLDivElement, ColorPickerProps>(
240
239
  max={100}
241
240
  step={1}
242
241
  onValueChange={([na]) => commit(h, s, v, (na ?? a * 100) / 100)}
243
- aria-label="Alpha"
244
242
  className="relative flex h-4 w-full touch-none select-none items-center"
245
243
  >
246
244
  <SliderPrimitive.Track
@@ -256,7 +254,7 @@ const ColorPicker = React.forwardRef<HTMLDivElement, ColorPickerProps>(
256
254
  style={{ backgroundImage: `linear-gradient(to right, transparent, hsl(${h} ${s}% ${v}%))` }}
257
255
  />
258
256
  </SliderPrimitive.Track>
259
- <SliderPrimitive.Thumb className="block size-4 rounded-full border-2 border-white bg-background shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" />
257
+ <SliderPrimitive.Thumb aria-label="Alpha" className="block size-4 rounded-full border-2 border-white bg-background shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" />
260
258
  </SliderPrimitive.Root>
261
259
  )}
262
260