@lerianstudio/sindarian-ui 1.2.0-beta.11 → 1.2.0-beta.12

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.
@@ -0,0 +1,2 @@
1
+ import '@testing-library/jest-dom';
2
+ //# sourceMappingURL=button.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.test.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/button/button.test.tsx"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAA"}
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ require("@testing-library/jest-dom");
5
+ const react_1 = require("@testing-library/react");
6
+ const _1 = require(".");
7
+ /**
8
+ * Regression coverage for `asChild`.
9
+ *
10
+ * Button always renders three children (the two icon slots evaluate to `false`
11
+ * when no `icon` is given). Without a `Slottable` marking the real child, Radix
12
+ * Slot >=1.3.0 cannot tell which of the three to merge onto and throws
13
+ * "Slot failed to slot onto its children" — which broke `<Button asChild>` for
14
+ * every consumer, icon or not.
15
+ */
16
+ describe('Button asChild', () => {
17
+ it('renders the child element instead of a button, with no icon', () => {
18
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.Button, { asChild: true, variant: "link", children: (0, jsx_runtime_1.jsx)("a", { href: "/somewhere", children: "Go somewhere" }) }));
19
+ const link = react_1.screen.getByRole('link', { name: 'Go somewhere' });
20
+ expect(link).toBeInTheDocument();
21
+ expect(link).toHaveAttribute('href', '/somewhere');
22
+ // asChild must NOT leave a wrapping <button> around the anchor.
23
+ expect(react_1.screen.queryByRole('button')).not.toBeInTheDocument();
24
+ });
25
+ it('merges the button classes onto the child element', () => {
26
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.Button, { asChild: true, variant: "link", size: "small", className: "custom-class", children: (0, jsx_runtime_1.jsx)("a", { href: "/x", children: "Styled" }) }));
27
+ const link = react_1.screen.getByRole('link', { name: 'Styled' });
28
+ expect(link).toHaveClass('custom-class');
29
+ expect(link).toHaveAttribute('data-slot', 'button');
30
+ });
31
+ it('keeps a start icon alongside the slotted child', () => {
32
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.Button, { asChild: true, icon: (0, jsx_runtime_1.jsx)("span", { "data-testid": "icon", children: "*" }), children: (0, jsx_runtime_1.jsx)("a", { href: "/x", children: "With icon" }) }));
33
+ const link = react_1.screen.getByRole('link', { name: /With icon/ });
34
+ expect(link).toBeInTheDocument();
35
+ // The icon renders inside the slotted element, not as a sibling of it.
36
+ expect(link).toContainElement(react_1.screen.getByTestId('icon'));
37
+ });
38
+ it('keeps an end icon alongside the slotted child', () => {
39
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.Button, { asChild: true, icon: (0, jsx_runtime_1.jsx)("span", { "data-testid": "icon", children: "*" }), iconPlacement: "end", children: (0, jsx_runtime_1.jsx)("a", { href: "/x", children: "Trailing" }) }));
40
+ expect(react_1.screen.getByRole('link', { name: /Trailing/ })).toContainElement(react_1.screen.getByTestId('icon'));
41
+ });
42
+ it('accepts a child whose own content is multiple nodes', () => {
43
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.Button, { asChild: true, variant: "link", children: (0, jsx_runtime_1.jsxs)("a", { href: "/x", children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: "\u21A9" }), "Back to list"] }) }));
44
+ expect(react_1.screen.getByRole('link', { name: 'Back to list' })).toBeInTheDocument();
45
+ });
46
+ });
47
+ describe('Button without asChild', () => {
48
+ it('still renders a native button (the Slottable path must be transparent)', () => {
49
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.Button, { variant: "primary", children: "Click me" }));
50
+ const button = react_1.screen.getByRole('button', { name: 'Click me' });
51
+ expect(button.tagName).toBe('BUTTON');
52
+ expect(button).toHaveAttribute('data-slot', 'button');
53
+ });
54
+ it('still renders its icon', () => {
55
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.Button, { icon: (0, jsx_runtime_1.jsx)("span", { "data-testid": "icon", children: "*" }), children: "Has an icon" }));
56
+ expect(react_1.screen.getByTestId('icon')).toBeInTheDocument();
57
+ expect(react_1.screen.getByRole('button', { name: /Has an icon/ })).toBeInTheDocument();
58
+ });
59
+ it('suppresses onClick when readOnly', () => {
60
+ const onClick = jest.fn();
61
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.Button, { readOnly: true, onClick: onClick, children: "Read only" }));
62
+ react_1.screen.getByRole('button', { name: 'Read only' }).click();
63
+ expect(onClick).not.toHaveBeenCalled();
64
+ });
65
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/button/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,QAAA,MAAM,cAAc;;;;8EA0BlB,CAAA;AAEF,QAAA,MAAM,YAAY;;;8EAgBhB,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,GACtD,YAAY,CAAC,OAAO,cAAc,CAAC,GAAG;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,GAAG;IACF,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,aAAa,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,SAAS,CAAA;IAC3C,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAEH,iBAAS,MAAM,CAAC,EACd,SAAS,EACT,OAAO,EACP,IAAI,EACJ,OAAe,EACf,IAAI,EACJ,aAAuB,EACvB,SAAiB,EACjB,QAAgB,EAChB,OAAO,EACP,GAAG,KAAK,EACT,EAAE,WAAW,qBAgCb;AAED,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/button/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,QAAA,MAAM,cAAc;;;;8EA0BlB,CAAA;AAEF,QAAA,MAAM,YAAY;;;8EAgBhB,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,GACtD,YAAY,CAAC,OAAO,cAAc,CAAC,GAAG;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,GAAG;IACF,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,aAAa,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,SAAS,CAAA;IAC3C,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAEH,iBAAS,MAAM,CAAC,EACd,SAAS,EACT,OAAO,EACP,IAAI,EACJ,OAAe,EACf,IAAI,EACJ,aAAuB,EACvB,SAAiB,EACjB,QAAgB,EAChB,OAAO,EACP,GAAG,KAAK,EACT,EAAE,WAAW,qBAqCb;AAED,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,CAAA"}
@@ -60,5 +60,5 @@ function Button({ className, variant, size, asChild = false, icon, iconPlacement
60
60
  }
61
61
  onClick?.(e);
62
62
  };
63
- return ((0, jsx_runtime_1.jsxs)(Comp, { className: (0, utils_1.cn)(buttonVariants({ variant, size, fullWidth }), className), "data-read-only": readOnly, "data-slot": "button", ...props, onClick: handleClick, children: [icon && iconPlacement === 'start' && ((0, jsx_runtime_1.jsx)("span", { className: (0, utils_1.cn)(iconVariants({ position: iconPlacement, size })), children: icon })), props.children, icon && iconPlacement !== 'start' && ((0, jsx_runtime_1.jsx)("span", { className: (0, utils_1.cn)(iconVariants({ position: iconPlacement, size })), children: icon }))] }));
63
+ return ((0, jsx_runtime_1.jsxs)(Comp, { className: (0, utils_1.cn)(buttonVariants({ variant, size, fullWidth }), className), "data-read-only": readOnly, "data-slot": "button", ...props, onClick: handleClick, children: [icon && iconPlacement === 'start' && ((0, jsx_runtime_1.jsx)("span", { className: (0, utils_1.cn)(iconVariants({ position: iconPlacement, size })), children: icon })), (0, jsx_runtime_1.jsx)(react_slot_1.Slottable, { children: props.children }), icon && iconPlacement !== 'start' && ((0, jsx_runtime_1.jsx)("span", { className: (0, utils_1.cn)(iconVariants({ position: iconPlacement, size })), children: icon }))] }));
64
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/input/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,EAAO,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,QAAA,MAAM,aAAa;;;8EAejB,CAAA;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,IAAI,EAAE,MAAM,IAAI,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,IAAI,CAC3B,YAAY,CAAC,OAAO,aAAa,CAAC,EAClC,gBAAgB,GAAG,cAAc,CAClC,GACC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG;IAC9B,cAAc,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;CAC1B,CAAA;AAEH,wBAAgB,KAAK,CAAC,EACpB,SAAS,EACT,cAAc,EACd,YAAY,EACZ,OAAO,EACP,MAAM,EACN,OAAO,EACP,GAAG,EACH,GAAG,KAAK,EACT,EAAE,UAAU,qBAuEZ;AAED,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/input/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,EAAO,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,QAAA,MAAM,aAAa;;;8EAejB,CAAA;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,IAAI,EAAE,MAAM,IAAI,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,IAAI,CAC3B,YAAY,CAAC,OAAO,aAAa,CAAC,EAClC,gBAAgB,GAAG,cAAc,CAClC,GACC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG;IAC9B,cAAc,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;CAC1B,CAAA;AAEH,wBAAgB,KAAK,CAAC,EACpB,SAAS,EACT,cAAc,EACd,YAAY,EACZ,OAAO,EACP,MAAM,EACN,OAAO,EACP,GAAG,EACH,GAAG,KAAK,EACT,EAAE,UAAU,qBAyEZ;AAED,cAAc,aAAa,CAAA"}
@@ -40,7 +40,6 @@ exports.Input = Input;
40
40
  const jsx_runtime_1 = require("react/jsx-runtime");
41
41
  const React = __importStar(require("react"));
42
42
  const utils_1 = require("../../../lib/utils");
43
- const form_1 = require("../../../components/ui/form");
44
43
  const class_variance_authority_1 = require("class-variance-authority");
45
44
  const inputVariants = (0, class_variance_authority_1.cva)('input-base input-disabled input-read-only', {
46
45
  variants: {
@@ -59,7 +58,9 @@ const inputVariants = (0, class_variance_authority_1.cva)('input-base input-disa
59
58
  }
60
59
  });
61
60
  function Input({ className, startAdornment, endAdornment, onFocus, onBlur, onClick, ref, ...props }) {
62
- const { formItemId } = (0, form_1.useFormField)();
61
+ // Fallback id so a standalone Input is still labelable. Inside a form,
62
+ // FormControl injects its own id through `props` below and overrides this.
63
+ const generatedId = React.useId();
63
64
  const [focus, setFocus] = React.useState(false);
64
65
  const inputRef = React.useRef(null);
65
66
  React.useImperativeHandle(ref, () => ({
@@ -90,7 +91,7 @@ function Input({ className, startAdornment, endAdornment, onFocus, onBlur, onCli
90
91
  setFocus(false);
91
92
  onBlur?.(e);
92
93
  };
93
- return ((0, jsx_runtime_1.jsxs)("div", { "data-slot": "input-wrapper", className: (0, utils_1.cn)('input-wrapper input-wrapper-focus'), "data-focus": focus, onClick: handleWrapperClick, children: [startAdornment, (0, jsx_runtime_1.jsx)("input", { ref: inputRef, id: formItemId, "data-slot": "input", className: (0, utils_1.cn)(inputVariants({
94
+ return ((0, jsx_runtime_1.jsxs)("div", { "data-slot": "input-wrapper", className: (0, utils_1.cn)('input-wrapper input-wrapper-focus'), "data-focus": focus, onClick: handleWrapperClick, children: [startAdornment, (0, jsx_runtime_1.jsx)("input", { ref: inputRef, id: generatedId, "data-slot": "input", className: (0, utils_1.cn)(inputVariants({
94
95
  startAdornment: !!startAdornment,
95
96
  endAdornment: !!endAdornment
96
97
  }), className), onFocus: handleFocus, onBlur: handleBlur, ...props }), endAdornment] }));
@@ -0,0 +1,2 @@
1
+ import '@testing-library/jest-dom';
2
+ //# sourceMappingURL=input.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.test.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/input/input.test.tsx"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAA"}
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ require("@testing-library/jest-dom");
5
+ const react_1 = require("@testing-library/react");
6
+ const react_hook_form_1 = require("react-hook-form");
7
+ const _1 = require(".");
8
+ const form_1 = require("../form");
9
+ /**
10
+ * Regression coverage for standalone use.
11
+ *
12
+ * `Input` is a primitive with two callers: `InputField`, which wraps it in
13
+ * `FormField`/`FormItem`, and plain standalone use such as a search box. It
14
+ * used to call `useFormField`, which destructures `useFormContext()` — null
15
+ * with no provider above — so every standalone `<Input />` threw "Cannot
16
+ * destructure property 'getFieldState' of useFormContext(...) as it is null".
17
+ *
18
+ * It now reads no form context at all: a local `useId` covers the standalone
19
+ * case, and inside a form `FormControl`'s Slot injects the real id as a prop.
20
+ */
21
+ describe('Input outside a form', () => {
22
+ it('renders with no react-hook-form provider', () => {
23
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.Input, { placeholder: "Search permissions by name..." }));
24
+ expect(react_1.screen.getByPlaceholderText('Search permissions by name...')).toBeInTheDocument();
25
+ });
26
+ it('still gets an id so a label can be associated with it', () => {
27
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.Input, { "aria-label": "Search" }));
28
+ expect(react_1.screen.getByRole('textbox', { name: 'Search' })).toHaveAttribute('id');
29
+ });
30
+ it('keeps value/onChange behaviour intact', () => {
31
+ const onChange = jest.fn();
32
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.Input, { value: "abc", onChange: onChange, "aria-label": "Search" }));
33
+ const input = react_1.screen.getByRole('textbox', { name: 'Search' });
34
+ expect(input).toHaveValue('abc');
35
+ react_1.fireEvent.change(input, { target: { value: 'abcd' } });
36
+ expect(onChange).toHaveBeenCalledTimes(1);
37
+ expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ target: input }));
38
+ });
39
+ });
40
+ function FormHarness() {
41
+ const form = (0, react_hook_form_1.useForm)({ defaultValues: { email: 'ada@example.com' } });
42
+ return ((0, jsx_runtime_1.jsx)(form_1.Form, { ...form, children: (0, jsx_runtime_1.jsx)(form_1.FormField, { control: form.control, name: "email", render: ({ field }) => ((0, jsx_runtime_1.jsxs)(form_1.FormItem, { children: [(0, jsx_runtime_1.jsx)(form_1.FormLabel, { children: "E-mail" }), (0, jsx_runtime_1.jsx)(form_1.FormControl, { children: (0, jsx_runtime_1.jsx)(_1.Input, { ...field }) })] })) }) }));
43
+ }
44
+ describe('Input inside a form', () => {
45
+ it('is still wired to its FormItem id, so the label points at it', () => {
46
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(FormHarness, {}));
47
+ const input = react_1.screen.getByLabelText('E-mail');
48
+ expect(input).toHaveValue('ada@example.com');
49
+ // The id must come from FormItem's context, not the standalone fallback.
50
+ expect(input.getAttribute('id')).toMatch(/-form-item$/);
51
+ });
52
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lerianstudio/sindarian-ui",
3
- "version": "1.2.0-beta.11",
3
+ "version": "1.2.0-beta.12",
4
4
  "description": "Sindarian UI - A UI library for Midaz Console",
5
5
  "license": "ISC",
6
6
  "author": {