@zentauri-ui/zentauri-components 1.7.2 → 1.7.4

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 (69) hide show
  1. package/README.md +10 -6
  2. package/cli/registry.json +2 -0
  3. package/dist/chunk-KEKMMNL5.mjs +600 -0
  4. package/dist/chunk-KEKMMNL5.mjs.map +1 -0
  5. package/dist/chunk-NZDHSIIC.js +616 -0
  6. package/dist/chunk-NZDHSIIC.js.map +1 -0
  7. package/dist/design-system/command.d.ts +41 -0
  8. package/dist/design-system/command.d.ts.map +1 -0
  9. package/dist/design-system/index.d.ts +2 -0
  10. package/dist/design-system/index.d.ts.map +1 -1
  11. package/dist/design-system/otp-input.d.ts +27 -0
  12. package/dist/design-system/otp-input.d.ts.map +1 -0
  13. package/dist/ui/command/animated/animations.d.ts +3 -0
  14. package/dist/ui/command/animated/animations.d.ts.map +1 -0
  15. package/dist/ui/command/animated/command-content-animated.d.ts +6 -0
  16. package/dist/ui/command/animated/command-content-animated.d.ts.map +1 -0
  17. package/dist/ui/command/animated/index.d.ts +4 -0
  18. package/dist/ui/command/animated/index.d.ts.map +1 -0
  19. package/dist/ui/command/animated/types.d.ts +9 -0
  20. package/dist/ui/command/animated/types.d.ts.map +1 -0
  21. package/dist/ui/command/animated.js +92 -0
  22. package/dist/ui/command/animated.js.map +1 -0
  23. package/dist/ui/command/animated.mjs +89 -0
  24. package/dist/ui/command/animated.mjs.map +1 -0
  25. package/dist/ui/command/command-base.d.ts +53 -0
  26. package/dist/ui/command/command-base.d.ts.map +1 -0
  27. package/dist/ui/command/command.d.ts +6 -0
  28. package/dist/ui/command/command.d.ts.map +1 -0
  29. package/dist/ui/command/index.d.ts +5 -0
  30. package/dist/ui/command/index.d.ts.map +1 -0
  31. package/dist/ui/command/types.d.ts +111 -0
  32. package/dist/ui/command/types.d.ts.map +1 -0
  33. package/dist/ui/command/variants.d.ts +15 -0
  34. package/dist/ui/command/variants.d.ts.map +1 -0
  35. package/dist/ui/command.js +69 -0
  36. package/dist/ui/command.js.map +1 -0
  37. package/dist/ui/command.mjs +16 -0
  38. package/dist/ui/command.mjs.map +1 -0
  39. package/dist/ui/otp-input/index.d.ts +4 -0
  40. package/dist/ui/otp-input/index.d.ts.map +1 -0
  41. package/dist/ui/otp-input/otp-input.d.ts +6 -0
  42. package/dist/ui/otp-input/otp-input.d.ts.map +1 -0
  43. package/dist/ui/otp-input/types.d.ts +23 -0
  44. package/dist/ui/otp-input/types.d.ts.map +1 -0
  45. package/dist/ui/otp-input/variants.d.ts +5 -0
  46. package/dist/ui/otp-input/variants.d.ts.map +1 -0
  47. package/dist/ui/otp-input.js +302 -0
  48. package/dist/ui/otp-input.js.map +1 -0
  49. package/dist/ui/otp-input.mjs +299 -0
  50. package/dist/ui/otp-input.mjs.map +1 -0
  51. package/package.json +1 -1
  52. package/src/design-system/command.ts +80 -0
  53. package/src/design-system/index.ts +2 -0
  54. package/src/design-system/otp-input.ts +50 -0
  55. package/src/ui/command/animated/animations.ts +29 -0
  56. package/src/ui/command/animated/command-content-animated.tsx +58 -0
  57. package/src/ui/command/animated/index.ts +10 -0
  58. package/src/ui/command/animated/types.ts +23 -0
  59. package/src/ui/command/command-base.tsx +660 -0
  60. package/src/ui/command/command.test.tsx +130 -0
  61. package/src/ui/command/command.tsx +8 -0
  62. package/src/ui/command/index.ts +34 -0
  63. package/src/ui/command/types.ts +129 -0
  64. package/src/ui/command/variants.ts +41 -0
  65. package/src/ui/otp-input/index.ts +9 -0
  66. package/src/ui/otp-input/otp-input.test.tsx +99 -0
  67. package/src/ui/otp-input/otp-input.tsx +327 -0
  68. package/src/ui/otp-input/types.ts +32 -0
  69. package/src/ui/otp-input/variants.ts +18 -0
@@ -0,0 +1,130 @@
1
+ import { render, screen, waitFor } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, expect, it, vi } from "vitest";
4
+
5
+ import { Command } from "./command";
6
+ import {
7
+ CommandContent,
8
+ CommandEmpty,
9
+ CommandGroup,
10
+ CommandInput,
11
+ CommandItem,
12
+ CommandList,
13
+ CommandSeparator,
14
+ CommandTrigger,
15
+ } from "./command-base";
16
+
17
+ function Palette({
18
+ open,
19
+ onOpenChange,
20
+ onSelectHome,
21
+ }: {
22
+ open?: boolean;
23
+ onOpenChange?: (next: boolean) => void;
24
+ onSelectHome?: (value: string) => void;
25
+ } = {}) {
26
+ return (
27
+ <Command open={open} onOpenChange={onOpenChange}>
28
+ <CommandTrigger>Open palette</CommandTrigger>
29
+ <CommandContent>
30
+ <CommandInput placeholder="Search…" />
31
+ <CommandList>
32
+ <CommandEmpty>No results found.</CommandEmpty>
33
+ <CommandGroup heading="Navigation">
34
+ <CommandItem value="home" onSelect={onSelectHome}>
35
+ Home
36
+ </CommandItem>
37
+ <CommandItem value="settings">Settings</CommandItem>
38
+ </CommandGroup>
39
+ <CommandSeparator />
40
+ <CommandGroup heading="Actions">
41
+ <CommandItem value="new-project">Create project</CommandItem>
42
+ </CommandGroup>
43
+ </CommandList>
44
+ </CommandContent>
45
+ </Command>
46
+ );
47
+ }
48
+
49
+ describe("Command", () => {
50
+ it("should open from the trigger and render a dialog", async () => {
51
+ const user = userEvent.setup();
52
+ render(<Palette />);
53
+ await user.click(screen.getByRole("button", { name: "Open palette" }));
54
+ await waitFor(() =>
55
+ expect(screen.getByRole("dialog")).toBeInTheDocument(),
56
+ );
57
+ expect(screen.getByText("Home")).toBeInTheDocument();
58
+ });
59
+
60
+ it("should respect controlled open state", async () => {
61
+ const { rerender } = render(<Palette open={false} />);
62
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
63
+ rerender(<Palette open />);
64
+ await waitFor(() =>
65
+ expect(screen.getByRole("dialog")).toBeInTheDocument(),
66
+ );
67
+ });
68
+
69
+ it("should filter items as the user types", async () => {
70
+ const user = userEvent.setup();
71
+ render(<Palette open />);
72
+ await waitFor(() =>
73
+ expect(screen.getByRole("dialog")).toBeInTheDocument(),
74
+ );
75
+ await user.type(screen.getByRole("combobox"), "settings");
76
+ await waitFor(() => {
77
+ expect(screen.getByText("Settings")).toBeVisible();
78
+ expect(screen.getByText("Home").closest("[data-slot=command-item]")).toHaveAttribute(
79
+ "hidden",
80
+ );
81
+ });
82
+ });
83
+
84
+ it("should show the empty state when nothing matches", async () => {
85
+ const user = userEvent.setup();
86
+ render(<Palette open />);
87
+ await user.type(screen.getByRole("combobox"), "zzzzzz");
88
+ await waitFor(() =>
89
+ expect(screen.getByText("No results found.")).toBeInTheDocument(),
90
+ );
91
+ });
92
+
93
+ it("should select the active item with arrow keys and Enter", async () => {
94
+ const user = userEvent.setup();
95
+ const onSelectHome = vi.fn();
96
+ render(<Palette open onSelectHome={onSelectHome} />);
97
+ await waitFor(() =>
98
+ expect(screen.getByRole("dialog")).toBeInTheDocument(),
99
+ );
100
+ const input = screen.getByRole("combobox");
101
+ input.focus();
102
+ await user.keyboard("{Enter}");
103
+ expect(onSelectHome).toHaveBeenCalledWith("home");
104
+ });
105
+
106
+ it("should close when Escape is pressed", async () => {
107
+ const user = userEvent.setup();
108
+ const onOpenChange = vi.fn();
109
+ render(<Palette open onOpenChange={onOpenChange} />);
110
+ await waitFor(() =>
111
+ expect(screen.getByRole("dialog")).toBeInTheDocument(),
112
+ );
113
+ await user.keyboard("{Escape}");
114
+ await waitFor(() =>
115
+ expect(onOpenChange).toHaveBeenLastCalledWith(false),
116
+ );
117
+ });
118
+
119
+ it("should render group headings and a separator", async () => {
120
+ render(<Palette open />);
121
+ await waitFor(() =>
122
+ expect(screen.getByRole("dialog")).toBeInTheDocument(),
123
+ );
124
+ expect(screen.getByText("Navigation")).toBeInTheDocument();
125
+ expect(screen.getByText("Actions")).toBeInTheDocument();
126
+ expect(
127
+ document.querySelector('[data-slot="command-separator"]'),
128
+ ).toBeTruthy();
129
+ });
130
+ });
@@ -0,0 +1,8 @@
1
+ import { Command as CommandBase } from "./command-base";
2
+ import type { CommandProps } from "./types";
3
+
4
+ export const Command = (props: CommandProps) => {
5
+ return <CommandBase {...props} />;
6
+ };
7
+
8
+ Command.displayName = "Command";
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ export { Command } from "./command";
4
+ export {
5
+ CommandTrigger,
6
+ CommandContent,
7
+ CommandInput,
8
+ CommandList,
9
+ CommandGroup,
10
+ CommandItem,
11
+ CommandSeparator,
12
+ CommandEmpty,
13
+ CommandFooter,
14
+ useCommandContext,
15
+ } from "./command-base";
16
+ export type {
17
+ CommandProps,
18
+ CommandTriggerProps,
19
+ CommandContentProps,
20
+ CommandContentVariantProps,
21
+ CommandInputProps,
22
+ CommandListProps,
23
+ CommandGroupProps,
24
+ CommandItemProps,
25
+ CommandSectionProps,
26
+ CommandCtx,
27
+ ItemMeta,
28
+ RegisteredItem
29
+ } from "./types";
30
+ export {
31
+ commandContentVariants,
32
+ commandOverlayVariants,
33
+ commandItemVariants,
34
+ } from "./variants";
@@ -0,0 +1,129 @@
1
+ import type { VariantProps } from "class-variance-authority";
2
+ import type { CSSProperties, ReactNode, Ref, RefObject } from "react";
3
+
4
+ import type { commandContentVariants } from "./variants";
5
+
6
+ export type CommandContentVariantProps = VariantProps<
7
+ typeof commandContentVariants
8
+ >;
9
+
10
+ export type CommandProps = {
11
+ open?: boolean;
12
+ defaultOpen?: boolean;
13
+ onOpenChange?: (open: boolean) => void;
14
+ /**
15
+ * When set, binds a global shortcut that toggles the palette: this key plus
16
+ * meta (⌘) or ctrl. Defaults to "k" when `hotkey` is `true`.
17
+ */
18
+ hotkey?: string | boolean;
19
+ /** Accessible label for the dialog. */
20
+ label?: string;
21
+ children?: ReactNode;
22
+ };
23
+
24
+ export type CommandTriggerProps = {
25
+ className?: string;
26
+ children?: ReactNode;
27
+ ref?: Ref<HTMLButtonElement>;
28
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
29
+ };
30
+
31
+ export type CommandContentProps = CommandContentVariantProps & {
32
+ className?: string;
33
+ children?: ReactNode;
34
+ ref?: Ref<HTMLDivElement>;
35
+ id?: string;
36
+ style?: CSSProperties;
37
+ };
38
+
39
+ export type CommandInputProps = {
40
+ className?: string;
41
+ placeholder?: string;
42
+ ref?: Ref<HTMLInputElement>;
43
+ };
44
+
45
+ export type CommandListProps = {
46
+ className?: string;
47
+ children?: ReactNode;
48
+ };
49
+
50
+ export type CommandGroupProps = {
51
+ className?: string;
52
+ heading?: ReactNode;
53
+ children?: ReactNode;
54
+ };
55
+
56
+ export type CommandItemProps = {
57
+ className?: string;
58
+ /** Stable identifier used for filtering, keyboard nav, and onSelect. */
59
+ value: string;
60
+ /** Extra terms used by the filter in addition to the rendered text. */
61
+ keywords?: string[];
62
+ disabled?: boolean;
63
+ onSelect?: (value: string) => void;
64
+ children?: ReactNode;
65
+ };
66
+
67
+ export type CommandSectionProps = {
68
+ className?: string;
69
+ children?: ReactNode;
70
+ };
71
+
72
+ export type ItemMeta = {
73
+ keywords?: string[];
74
+ disabled?: boolean;
75
+ onSelect?: (value: string) => void;
76
+ searchText?: string;
77
+ };
78
+
79
+ export type RegisteredItem = {
80
+ value: string;
81
+ metaRef: RefObject<ItemMeta>;
82
+ };
83
+
84
+ export type CommandCtx = {
85
+ open: boolean;
86
+ setOpen: (next: boolean) => void;
87
+ labelId: string;
88
+ listId: string;
89
+ query: string;
90
+ setQuery: (next: string) => void;
91
+ activeValue: string | null;
92
+ setActiveValue: (next: string | null) => void;
93
+ visibleValues: string[];
94
+ isVisible: (value: string) => boolean;
95
+ registerItem: (item: RegisteredItem) => () => void;
96
+ invalidateRegistry: () => void;
97
+ selectValue: (value: string) => boolean;
98
+ contentRef: RefObject<HTMLDivElement | null>;
99
+ triggerRef: RefObject<HTMLElement | null>;
100
+ inputRef: RefObject<HTMLInputElement | null>;
101
+ };
102
+
103
+
104
+ export type CommandContentOverlayRenderProps = {
105
+ role: "presentation";
106
+ "data-slot": "command-overlay";
107
+ className: string;
108
+ onClick: () => void;
109
+ };
110
+
111
+ export type CommandContentPanelRenderProps = {
112
+ ref: (node: HTMLDivElement | null) => void;
113
+ role: "dialog";
114
+ "aria-modal": true;
115
+ "aria-labelledby": string;
116
+ "data-slot": "command-content";
117
+ tabIndex: -1;
118
+ className: string;
119
+ id?: string;
120
+ style?: CommandContentProps["style"];
121
+ children: ReactNode;
122
+ };
123
+
124
+ export type CommandContentLayerProps = CommandContentProps & {
125
+ componentName: string;
126
+ renderPresence?: (children: ReactNode) => ReactNode;
127
+ renderOverlay?: (props: CommandContentOverlayRenderProps) => ReactNode;
128
+ renderPanel?: (props: CommandContentPanelRenderProps) => ReactNode;
129
+ };
@@ -0,0 +1,41 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ import {
4
+ zuiCommandContentAppearances,
5
+ zuiCommandContentBase,
6
+ zuiCommandContentSizes,
7
+ zuiCommandEmptyBase,
8
+ zuiCommandFooterBase,
9
+ zuiCommandGroupHeadingBase,
10
+ zuiCommandInputBase,
11
+ zuiCommandInputRowBase,
12
+ zuiCommandItemBase,
13
+ zuiCommandListBase,
14
+ zuiCommandOverlayBase,
15
+ zuiCommandSeparatorBase,
16
+ zuiCommandTriggerBase,
17
+ } from "../../design-system/command";
18
+
19
+ export const commandOverlayVariants = cva(zuiCommandOverlayBase);
20
+
21
+ export const commandTriggerVariants = cva(zuiCommandTriggerBase);
22
+
23
+ export const commandContentVariants = cva(zuiCommandContentBase, {
24
+ variants: {
25
+ size: zuiCommandContentSizes,
26
+ appearance: zuiCommandContentAppearances,
27
+ },
28
+ defaultVariants: {
29
+ size: "md",
30
+ appearance: "default",
31
+ },
32
+ });
33
+
34
+ export const commandInputRowVariants = cva(zuiCommandInputRowBase);
35
+ export const commandInputVariants = cva(zuiCommandInputBase);
36
+ export const commandListVariants = cva(zuiCommandListBase);
37
+ export const commandGroupHeadingVariants = cva(zuiCommandGroupHeadingBase);
38
+ export const commandItemVariants = cva(zuiCommandItemBase);
39
+ export const commandSeparatorVariants = cva(zuiCommandSeparatorBase);
40
+ export const commandEmptyVariants = cva(zuiCommandEmptyBase);
41
+ export const commandFooterVariants = cva(zuiCommandFooterBase);
@@ -0,0 +1,9 @@
1
+ "use client";
2
+
3
+ export { OTPInput } from "./otp-input";
4
+ export type {
5
+ OTPInputAllowedCharacters,
6
+ OTPInputCellVariantProps,
7
+ OTPInputProps,
8
+ } from "./types";
9
+ export { otpInputCellVariants } from "./variants";
@@ -0,0 +1,99 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { describe, expect, it, vi } from "vitest";
5
+
6
+ import { OTPInput } from "./otp-input";
7
+
8
+ describe("OTPInput", () => {
9
+ it("should expose displayName", () => {
10
+ expect(OTPInput.displayName).toBe("OTPInput");
11
+ });
12
+
13
+ it("should stamp data-slot", () => {
14
+ render(<OTPInput label="Verification code" />);
15
+ expect(document.querySelector('[data-slot="otp-input"]')).toBeTruthy();
16
+ expect(
17
+ document.querySelectorAll('[data-slot="otp-input-cell"]'),
18
+ ).toHaveLength(6);
19
+ });
20
+
21
+ it("should type numeric values across cells", async () => {
22
+ const user = userEvent.setup();
23
+ const onValueChange = vi.fn();
24
+ render(<OTPInput label="Code" onValueChange={onValueChange} />);
25
+
26
+ await user.type(screen.getByLabelText("Digit 1 of 6"), "123");
27
+
28
+ expect(screen.getByLabelText("Digit 1 of 6")).toHaveValue("1");
29
+ expect(screen.getByLabelText("Digit 2 of 6")).toHaveValue("2");
30
+ expect(screen.getByLabelText("Digit 3 of 6")).toHaveValue("3");
31
+ expect(onValueChange).toHaveBeenLastCalledWith("123");
32
+ });
33
+
34
+ it("should ignore non-numeric characters by default", async () => {
35
+ const user = userEvent.setup();
36
+ render(<OTPInput label="Code" />);
37
+
38
+ await user.type(screen.getByLabelText("Digit 1 of 6"), "a1b2");
39
+
40
+ expect(screen.getByLabelText("Digit 1 of 6")).toHaveValue("1");
41
+ expect(screen.getByLabelText("Digit 2 of 6")).toHaveValue("2");
42
+ });
43
+
44
+ it("should support alphanumeric values", async () => {
45
+ const user = userEvent.setup();
46
+ render(<OTPInput allowedCharacters="alphanumeric" label="Invite code" />);
47
+
48
+ await user.type(screen.getByLabelText("Digit 1 of 6"), "A7Z");
49
+
50
+ expect(screen.getByLabelText("Digit 1 of 6")).toHaveValue("A");
51
+ expect(screen.getByLabelText("Digit 2 of 6")).toHaveValue("7");
52
+ expect(screen.getByLabelText("Digit 3 of 6")).toHaveValue("Z");
53
+ });
54
+
55
+ it("should fill cells from paste and call onComplete", async () => {
56
+ const user = userEvent.setup();
57
+ const onComplete = vi.fn();
58
+ render(<OTPInput label="Code" onComplete={onComplete} />);
59
+
60
+ await user.click(screen.getByLabelText("Digit 1 of 6"));
61
+ await user.paste("12-34 56");
62
+
63
+ expect(screen.getByLabelText("Digit 6 of 6")).toHaveValue("6");
64
+ expect(onComplete).toHaveBeenLastCalledWith("123456");
65
+ });
66
+
67
+ it("should clear the previous cell when backspacing from an empty cell", async () => {
68
+ const user = userEvent.setup();
69
+ render(<OTPInput defaultValue="12" label="Code" />);
70
+
71
+ await user.click(screen.getByLabelText("Digit 3 of 6"));
72
+ await user.keyboard("{Backspace}");
73
+
74
+ expect(screen.getByLabelText("Digit 2 of 6")).toHaveValue("");
75
+ });
76
+
77
+ it("should support controlled state", async () => {
78
+ const user = userEvent.setup();
79
+ const onValueChange = vi.fn();
80
+ render(<OTPInput label="Code" value="98" onValueChange={onValueChange} />);
81
+
82
+ await user.type(screen.getByLabelText("Digit 3 of 6"), "7");
83
+
84
+ expect(screen.getByLabelText("Digit 1 of 6")).toHaveValue("9");
85
+ expect(screen.getByLabelText("Digit 2 of 6")).toHaveValue("8");
86
+ expect(onValueChange).toHaveBeenLastCalledWith("987");
87
+ });
88
+
89
+ it("should forward ref to the root", () => {
90
+ const ref = createRef<HTMLDivElement>();
91
+ render(<OTPInput ref={ref} label="Code" />);
92
+ expect(ref.current?.getAttribute("data-slot")).toBe("otp-input");
93
+ });
94
+
95
+ it("should render a hidden input when name is provided", () => {
96
+ render(<OTPInput defaultValue="123456" label="Code" name="otp" />);
97
+ expect(document.querySelector('input[name="otp"]')).toHaveValue("123456");
98
+ });
99
+ });