@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.
- package/dist/components/ui/button/button.test.d.ts +2 -0
- package/dist/components/ui/button/button.test.d.ts.map +1 -0
- package/dist/components/ui/button/button.test.js +65 -0
- package/dist/components/ui/button/index.d.ts.map +1 -1
- package/dist/components/ui/button/index.js +1 -1
- package/dist/components/ui/input/index.d.ts.map +1 -1
- package/dist/components/ui/input/index.js +4 -3
- package/dist/components/ui/input/input.test.d.ts +2 -0
- package/dist/components/ui/input/input.test.d.ts.map +1 -0
- package/dist/components/ui/input/input.test.js +52 -0
- package/package.json +1 -1
|
@@ -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,
|
|
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;
|
|
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
|
-
|
|
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:
|
|
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 @@
|
|
|
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
|
+
});
|