@kinetixui/ui 0.16.1 → 0.18.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 (67) hide show
  1. package/dist/index.d.ts +4 -4
  2. package/dist/index.js +498 -248
  3. package/package.json +3 -2
  4. package/src/components/audio-player.tsx +2 -2
  5. package/src/components/badge.tsx +2 -2
  6. package/src/components/banner.tsx +2 -2
  7. package/src/components/button.tsx +18 -14
  8. package/src/components/calendar.tsx +1 -1
  9. package/src/components/chart.tsx +2 -2
  10. package/src/components/checkbox.tsx +2 -2
  11. package/src/components/circular-progress.tsx +1 -1
  12. package/src/components/code-block.tsx +1 -1
  13. package/src/components/color-picker.tsx +6 -4
  14. package/src/components/data-grid.tsx +192 -11
  15. package/src/components/diff-viewer.tsx +9 -8
  16. package/src/components/fab.tsx +1 -1
  17. package/src/components/file-upload.tsx +13 -15
  18. package/src/components/inform.tsx +1 -1
  19. package/src/components/input-group.tsx +1 -1
  20. package/src/components/input-otp.tsx +2 -1
  21. package/src/components/input.tsx +3 -3
  22. package/src/components/json-viewer.tsx +12 -10
  23. package/src/components/kbd.tsx +1 -1
  24. package/src/components/list.tsx +41 -27
  25. package/src/components/markdown-editor.tsx +3 -2
  26. package/src/components/message-bubble.tsx +3 -3
  27. package/src/components/modal.tsx +2 -2
  28. package/src/components/multi-select.tsx +28 -5
  29. package/src/components/native-select.tsx +3 -3
  30. package/src/components/notification-center.tsx +2 -2
  31. package/src/components/progress.tsx +1 -1
  32. package/src/components/radio-group.tsx +3 -3
  33. package/src/components/scroll-area.tsx +1 -1
  34. package/src/components/select.tsx +3 -3
  35. package/src/components/skeleton.tsx +1 -1
  36. package/src/components/slider.tsx +8 -3
  37. package/src/components/sonner.tsx +1 -1
  38. package/src/components/spinner.tsx +3 -3
  39. package/src/components/stepper.tsx +3 -3
  40. package/src/components/switch.tsx +1 -1
  41. package/src/components/tab-bar.tsx +1 -1
  42. package/src/components/table-of-contents.tsx +1 -1
  43. package/src/components/tag.tsx +1 -1
  44. package/src/components/textarea.tsx +3 -3
  45. package/src/components/timeline.tsx +1 -1
  46. package/src/components/tooltip.tsx +1 -1
  47. package/src/components/tour.tsx +45 -2
  48. package/src/components/virtual-list.tsx +2 -1
  49. package/src/components-a11y.test.tsx +69 -0
  50. package/src/components-behavior.test.tsx +1 -1
  51. package/src/components-keyboard.test.tsx +774 -0
  52. package/src/lib/utils.test.ts +12 -0
  53. package/src/lib/utils.ts +22 -1
  54. package/src/stories/Checkbox.stories.tsx +1 -1
  55. package/src/stories/CircularProgress.stories.tsx +4 -4
  56. package/src/stories/InputOTP.stories.tsx +2 -2
  57. package/src/stories/List.stories.tsx +2 -2
  58. package/src/stories/MarkdownEditor.stories.tsx +1 -1
  59. package/src/stories/MultiSelect.stories.tsx +1 -0
  60. package/src/stories/NativeSelect.stories.tsx +1 -1
  61. package/src/stories/NumberInput.stories.tsx +3 -3
  62. package/src/stories/Progress.stories.tsx +3 -3
  63. package/src/stories/Select.stories.tsx +2 -2
  64. package/src/stories/Slider.stories.tsx +3 -3
  65. package/src/stories/Switch.stories.tsx +1 -1
  66. package/src/stories/ToggleGroup.stories.tsx +6 -6
  67. package/tailwind.config.ts +20 -1
@@ -0,0 +1,12 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { cn } from "./utils";
3
+
4
+ describe("cn", () => {
5
+ it("keeps a type-scale class and a text colour together", () => {
6
+ expect(cn("text-primary-foreground", "text-label-sm")).toBe("text-primary-foreground text-label-sm");
7
+ expect(cn("text-label-sm", "text-primary-foreground")).toBe("text-label-sm text-primary-foreground");
8
+ });
9
+ it("still lets a later type-scale class override an earlier one", () => {
10
+ expect(cn("text-label-sm", "text-body-md")).toBe("text-body-md");
11
+ });
12
+ });
package/src/lib/utils.ts CHANGED
@@ -1,5 +1,26 @@
1
1
  import { type ClassValue, clsx } from "clsx";
2
- import { twMerge } from "tailwind-merge";
2
+ import { extendTailwindMerge } from "tailwind-merge";
3
+
4
+ // The Material type-scale utilities (`text-label-md`, `text-body-sm`, …) come
5
+ // from `fontSize` in tailwind.config.ts. Without this, tailwind-merge reads them
6
+ // as text *colours* and drops a real colour class (`text-primary-foreground`).
7
+ const twMerge = extendTailwindMerge({
8
+ extend: {
9
+ classGroups: {
10
+ "font-size": [
11
+ {
12
+ text: [
13
+ "display-lg", "display-md", "display-sm",
14
+ "headline-lg", "headline-md", "headline-sm",
15
+ "title-lg", "title-md", "title-sm", "title-dialog",
16
+ "label-lg", "label-md", "label-sm",
17
+ "body-lg", "body-md", "body-sm",
18
+ ],
19
+ },
20
+ ],
21
+ },
22
+ },
23
+ });
3
24
 
4
25
  /** Merges Tailwind class names, resolving conflicts (clsx + tailwind-merge). */
5
26
  export function cn(...inputs: ClassValue[]) {
@@ -24,6 +24,6 @@ const meta = {
24
24
 
25
25
  export default meta;
26
26
 
27
- export const Playground: StoryObj<typeof meta> = { render: (args) => <Checkbox {...args} /> };
27
+ export const Playground: StoryObj<typeof meta> = { render: (args) => <Checkbox aria-label="Accept terms" {...args} /> };
28
28
 
29
29
  export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -12,9 +12,9 @@ const Demo = () => {
12
12
  }, []);
13
13
  return (
14
14
  <div className="flex items-center gap-6">
15
- <CircularProgress value={v} showValue />
16
- <CircularProgress value={v} size={64} strokeWidth={6} showValue />
17
- <CircularProgress value={v} />
15
+ <CircularProgress value={v} showValue aria-label="Upload progress" />
16
+ <CircularProgress value={v} size={64} strokeWidth={6} showValue aria-label="Upload progress" />
17
+ <CircularProgress value={v} aria-label="Upload progress" />
18
18
  </div>
19
19
  );
20
20
  };
@@ -37,6 +37,6 @@ const meta = {
37
37
 
38
38
  export default meta;
39
39
 
40
- export const Playground: StoryObj<typeof meta> = { render: (args) => <CircularProgress {...args} /> };
40
+ export const Playground: StoryObj<typeof meta> = { render: (args) => <CircularProgress aria-label="Upload progress" {...args} /> };
41
41
 
42
42
  export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -5,7 +5,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
5
5
  import { InputOTP, InputOTPGroup, InputOTPSlot } from "@kinetixui/ui";
6
6
 
7
7
  const Demo = () => (
8
- <InputOTP maxLength={6}>
8
+ <InputOTP maxLength={6} aria-label="One-time code">
9
9
  <InputOTPGroup>
10
10
  {Array.from({ length: 6 }).map((_, i) => (
11
11
  <InputOTPSlot key={i} index={i} />
@@ -18,7 +18,7 @@ const meta = {
18
18
  title: "Form Inputs/InputOTP",
19
19
  parameters: {
20
20
  layout: "centered",
21
- docs: { source: { code: "<InputOTP maxLength={6}>\\n <InputOTPGroup>\\n <InputOTPSlot index={0} />\\n ...\\n <InputOTPSlot index={5} />\\n </InputOTPGroup>\\n</InputOTP>", language: "tsx" } },
21
+ docs: { source: { code: "<InputOTP maxLength={6} aria-label=\\\"One-time code\\\">\\n <InputOTPGroup>\\n <InputOTPSlot index={0} />\\n ...\\n <InputOTPSlot index={5} />\\n </InputOTPGroup>\\n</InputOTP>", language: "tsx" } },
22
22
  },
23
23
  } satisfies Meta;
24
24
 
@@ -8,7 +8,7 @@ import { Bell, Settings, User } from "lucide-react";
8
8
  const Demo = () => (
9
9
  <List className="w-full max-w-sm rounded-md border">
10
10
  <ListItem leading={<User className="size-5 text-muted-foreground" />} title="Profile" description="Name, photo, and personal details" trailing={<Badge variant="outline">2</Badge>} onSelect={() => {}} />
11
- <ListItem leading={<Bell className="size-5 text-muted-foreground" />} title="Notifications" description="Push, email and SMS preferences" trailing={<Switch />} />
11
+ <ListItem leading={<Bell className="size-5 text-muted-foreground" />} title="Notifications" description="Push, email and SMS preferences" trailing={<Switch aria-label="Notifications" />} />
12
12
  <ListItem leading={<Settings className="size-5 text-muted-foreground" />} title="Settings" onSelect={() => {}} />
13
13
  </List>
14
14
  );
@@ -17,7 +17,7 @@ const meta = {
17
17
  title: "Data Display/List",
18
18
  parameters: {
19
19
  layout: "centered",
20
- docs: { source: { code: "<List>\\n <ListItem\\n leading={<User />}\\n title=\"Profile\"\\n description=\"Name, photo, and personal details\"\\n onSelect={() => router.push(\"/profile\")}\\n />\\n <ListItem leading={<Bell />} title=\"Notifications\" trailing={<Switch />} />\\n</List>", language: "tsx" } },
20
+ docs: { source: { code: "<List>\\n <ListItem\\n leading={<User />}\\n title=\"Profile\"\\n description=\"Name, photo, and personal details\"\\n onSelect={() => router.push(\"/profile\")}\\n />\\n <ListItem leading={<Bell />} title=\"Notifications\" trailing={<Switch aria-label=\"Notifications\" />} />\\n</List>", language: "tsx" } },
21
21
  },
22
22
  } satisfies Meta;
23
23
 
@@ -9,7 +9,7 @@ const Demo = () => {
9
9
  const [text, setText] = React.useState(
10
10
  "## Release notes\n\nShipped **MarkdownEditor** — a toolbar over a plain textarea, not `contenteditable`.\n\n- Bold, italic, links\n- Bulleted and numbered lists\n- An optional preview pane",
11
11
  );
12
- return <MarkdownEditor className="w-full" value={text} onChange={setText} rows={8} />;
12
+ return <MarkdownEditor aria-label="Release notes" className="w-full" value={text} onChange={setText} rows={8} />;
13
13
  };
14
14
 
15
15
  const meta = {
@@ -8,6 +8,7 @@ const Demo = () => {
8
8
  const [value, setValue] = React.useState(["react", "swiftui"]);
9
9
  return (
10
10
  <MultiSelect
11
+ aria-label="Platforms"
11
12
  className="w-full max-w-sm"
12
13
  value={value}
13
14
  onValueChange={setValue}
@@ -5,7 +5,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
5
5
  import { NativeSelect, NativeSelectOptGroup, NativeSelectOption, Select } from "@kinetixui/ui";
6
6
 
7
7
  const Demo = () => (
8
- <NativeSelect className="max-w-xs" defaultValue="">
8
+ <NativeSelect aria-label="Fruit" className="max-w-xs" defaultValue="">
9
9
  <NativeSelectOption value="" disabled>
10
10
  Select a fruit…
11
11
  </NativeSelectOption>
@@ -6,7 +6,7 @@ import { NumberInput } from "@kinetixui/ui";
6
6
 
7
7
  const Demo = () => {
8
8
  const [v, setV] = React.useState(2);
9
- return <NumberInput value={v} onChange={setV} min={0} max={10} className="w-32" />;
9
+ return <NumberInput aria-label="Quantity" value={v} onChange={setV} min={0} max={10} className="w-32" />;
10
10
  };
11
11
 
12
12
  const meta = {
@@ -18,12 +18,12 @@ const meta = {
18
18
  },
19
19
  parameters: {
20
20
  layout: "centered",
21
- docs: { source: { code: "<NumberInput value={qty} onChange={setQty} min={0} max={10} />", language: "tsx" } },
21
+ docs: { source: { code: "<NumberInput aria-label=\\\"Quantity\\\" value={qty} onChange={setQty} min={0} max={10} />", language: "tsx" } },
22
22
  },
23
23
  } satisfies Meta;
24
24
 
25
25
  export default meta;
26
26
 
27
- export const Playground: StoryObj<typeof meta> = { render: (args) => <NumberInput {...args} className="w-32" /> };
27
+ export const Playground: StoryObj<typeof meta> = { render: (args) => <NumberInput aria-label="Quantity" {...args} className="w-32" /> };
28
28
 
29
29
  export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -10,7 +10,7 @@ const Demo = () => {
10
10
  const t = setTimeout(() => setV(66), 600);
11
11
  return () => clearTimeout(t);
12
12
  }, []);
13
- return <Progress value={v} className="w-[60%]" />;
13
+ return <Progress value={v} aria-label="Upload progress" className="w-[60%]" />;
14
14
  };
15
15
 
16
16
  const meta = {
@@ -20,12 +20,12 @@ const meta = {
20
20
  argTypes: { value: { control: { type: "range", min: 0, max: 100 } } },
21
21
  parameters: {
22
22
  layout: "centered",
23
- docs: { source: { code: "const [value, setValue] = React.useState(13)\\n// ...\\n<Progress value={value} />", language: "tsx" } },
23
+ docs: { source: { code: "const [value, setValue] = React.useState(13)\\n// ...\\n<Progress value={value} aria-label=\\\"Upload progress\\\" />", language: "tsx" } },
24
24
  },
25
25
  } satisfies Meta;
26
26
 
27
27
  export default meta;
28
28
 
29
- export const Playground: StoryObj<typeof meta> = { render: (args) => <Progress {...args} className="w-60" /> };
29
+ export const Playground: StoryObj<typeof meta> = { render: (args) => <Progress aria-label="Upload progress" {...args} className="w-60" /> };
30
30
 
31
31
  export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -6,7 +6,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
6
6
 
7
7
  const Demo = () => (
8
8
  <Select>
9
- <SelectTrigger className="w-[220px]">
9
+ <SelectTrigger className="w-[220px]" aria-label="Fruit">
10
10
  <SelectValue placeholder="Select a fruit" />
11
11
  </SelectTrigger>
12
12
  <SelectContent>
@@ -32,7 +32,7 @@ export default meta;
32
32
 
33
33
  export const Playground: StoryObj<typeof meta> = { render: (args) => (
34
34
  <Select {...args}>
35
- <SelectTrigger className="w-[220px]">
35
+ <SelectTrigger className="w-[220px]" aria-label="Fruit">
36
36
  <SelectValue placeholder="Select a fruit" />
37
37
  </SelectTrigger>
38
38
  <SelectContent>
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import type { Meta, StoryObj } from "@storybook/react-vite";
5
5
  import { Slider } from "@kinetixui/ui";
6
6
 
7
- const Demo = () => <Slider defaultValue={[50]} max={100} step={1} className="w-[60%]" />;
7
+ const Demo = () => <Slider aria-label="Volume" defaultValue={[50]} max={100} step={1} className="w-[60%]" />;
8
8
 
9
9
  const meta = {
10
10
  title: "Form Inputs/Slider",
@@ -13,12 +13,12 @@ const meta = {
13
13
  argTypes: { disabled: { control: "boolean" } },
14
14
  parameters: {
15
15
  layout: "centered",
16
- docs: { source: { code: "<Slider defaultValue={[50]} max={100} step={1} />", language: "tsx" } },
16
+ docs: { source: { code: "<Slider aria-label=\"Volume\" defaultValue={[50]} max={100} step={1} />", language: "tsx" } },
17
17
  },
18
18
  } satisfies Meta;
19
19
 
20
20
  export default meta;
21
21
 
22
- export const Playground: StoryObj<typeof meta> = { render: (args) => <Slider {...args} defaultValue={[50]} max={100} step={1} className="w-60" /> };
22
+ export const Playground: StoryObj<typeof meta> = { render: (args) => <Slider aria-label="Volume" {...args} defaultValue={[50]} max={100} step={1} className="w-60" /> };
23
23
 
24
24
  export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -24,6 +24,6 @@ const meta = {
24
24
 
25
25
  export default meta;
26
26
 
27
- export const Playground: StoryObj<typeof meta> = { render: (args) => <Switch {...args} /> };
27
+ export const Playground: StoryObj<typeof meta> = { render: (args) => <Switch aria-label="Airplane mode" {...args} /> };
28
28
 
29
29
  export const Default: StoryObj<typeof meta> = { render: () => <Demo /> };
@@ -7,9 +7,9 @@ import { Bold, Italic, Underline } from "lucide-react";
7
7
 
8
8
  const Demo = () => (
9
9
  <ToggleGroup type="multiple">
10
- <ToggleGroupItem value="bold"><Bold className="size-4" /></ToggleGroupItem>
11
- <ToggleGroupItem value="italic"><Italic className="size-4" /></ToggleGroupItem>
12
- <ToggleGroupItem value="underline"><Underline className="size-4" /></ToggleGroupItem>
10
+ <ToggleGroupItem value="bold" aria-label="Bold"><Bold className="size-4" /></ToggleGroupItem>
11
+ <ToggleGroupItem value="italic" aria-label="Italic"><Italic className="size-4" /></ToggleGroupItem>
12
+ <ToggleGroupItem value="underline" aria-label="Underline"><Underline className="size-4" /></ToggleGroupItem>
13
13
  </ToggleGroup>
14
14
  );
15
15
 
@@ -28,9 +28,9 @@ export default meta;
28
28
 
29
29
  export const Playground: StoryObj<typeof meta> = { render: (args) => (
30
30
  <ToggleGroup {...args}>
31
- <ToggleGroupItem value="bold"><Bold className="size-4" /></ToggleGroupItem>
32
- <ToggleGroupItem value="italic"><Italic className="size-4" /></ToggleGroupItem>
33
- <ToggleGroupItem value="underline"><Underline className="size-4" /></ToggleGroupItem>
31
+ <ToggleGroupItem value="bold" aria-label="Bold"><Bold className="size-4" /></ToggleGroupItem>
32
+ <ToggleGroupItem value="italic" aria-label="Italic"><Italic className="size-4" /></ToggleGroupItem>
33
+ <ToggleGroupItem value="underline" aria-label="Underline"><Underline className="size-4" /></ToggleGroupItem>
34
34
  </ToggleGroup>
35
35
  ) };
36
36
 
@@ -59,8 +59,27 @@ export default {
59
59
  destructive: { DEFAULT: c("--destructive"), foreground: c("--destructive-foreground") },
60
60
  success: { DEFAULT: c("--success"), foreground: c("--success-foreground") },
61
61
  warning: { DEFAULT: c("--warning"), foreground: c("--warning-foreground") },
62
- info: { DEFAULT: c("--info"), foreground: c("--info-foreground") },
62
+ info: {
63
+ DEFAULT: c("--info"),
64
+ foreground: c("--info-foreground"),
65
+ // text on a tinted info surface (bg-info/10) — --info alone is 4.15:1 there
66
+ "on-container": c("--semantic-on-info-container"),
67
+ },
63
68
  tertiary: { DEFAULT: c("--tertiary"), foreground: c("--tertiary-foreground") },
69
+ // Role tokens layered over `primary` (which stays the shadcn-compatible source). `action`, `link`
70
+ // and `focus` default to it, so a theme that only sets `--primary` still works; override them
71
+ // individually to split the interactive colour, link colour and focus colour. `brand` is identity
72
+ // (logos, headers), not a control colour.
73
+ brand: { DEFAULT: c("--brand"), foreground: c("--brand-foreground") },
74
+ action: {
75
+ DEFAULT: c("--action"),
76
+ foreground: c("--action-foreground"),
77
+ // explicit states for native ports; on the web, hover/pressed derive from `action` (bg-action/90, /85)
78
+ hover: c("--action-hover"),
79
+ pressed: c("--action-pressed"),
80
+ },
81
+ link: c("--link"),
82
+ focus: c("--focus"),
64
83
  border: c("--border"),
65
84
  input: c("--input"),
66
85
  ring: c("--ring"),