@icomponents/react 0.1.0 → 0.1.2
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/package.json +3 -3
- package/src/components/password-input.tsx +1 -1
- package/src/components/search.tsx +2 -2
- package/src/components/skip-to-main.tsx +1 -1
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/dropdown-menu.tsx +2 -2
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/sheet.tsx +3 -3
- package/src/components/ui/sidebar.tsx +8 -8
- package/src/tailwind-engine.contract.test.ts +30 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@icomponents/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Кит компонентов для веба: shadcn по форме на Base UI, оболочка, темизация, контракт testid",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
},
|
|
123
123
|
"dependencies": {
|
|
124
124
|
"@base-ui/react": "^1.6.0",
|
|
125
|
-
"@icomponents/tokens": "
|
|
125
|
+
"@icomponents/tokens": "^0.1.2",
|
|
126
126
|
"@radix-ui/react-icons": "^1.3.2",
|
|
127
127
|
"class-variance-authority": "^0.7.1",
|
|
128
128
|
"clsx": "^2.1.1",
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
},
|
|
148
148
|
"devDependencies": {
|
|
149
149
|
"@happy-dom/global-registrator": "^20.10.6",
|
|
150
|
-
"@icomponents/typescript-config": "
|
|
150
|
+
"@icomponents/typescript-config": "0.0.0",
|
|
151
151
|
"@tanstack/react-router": "^1.170.18",
|
|
152
152
|
"@tanstack/react-table": "^8.21.3",
|
|
153
153
|
"@testing-library/dom": "^10.4.1",
|
|
@@ -25,7 +25,7 @@ export function PasswordInput({ className, disabled, ref, ...props }: PasswordIn
|
|
|
25
25
|
size="icon"
|
|
26
26
|
variant="ghost"
|
|
27
27
|
disabled={disabled}
|
|
28
|
-
className="absolute
|
|
28
|
+
className="absolute end-1 top-1/2 h-6 w-6 -translate-y-1/2 rounded-md text-muted-foreground"
|
|
29
29
|
onClick={() => setShowPassword((prev) => !prev)}
|
|
30
30
|
>
|
|
31
31
|
{showPassword ? <Eye size={18} /> : <EyeOff size={18} />}
|
|
@@ -26,11 +26,11 @@ export function Search({
|
|
|
26
26
|
>
|
|
27
27
|
<SearchIcon
|
|
28
28
|
aria-hidden="true"
|
|
29
|
-
className="absolute
|
|
29
|
+
className="absolute start-1.5 top-1/2 -translate-y-1/2"
|
|
30
30
|
size={16}
|
|
31
31
|
/>
|
|
32
32
|
<span className="ms-4">{label}</span>
|
|
33
|
-
<kbd className="pointer-events-none absolute
|
|
33
|
+
<kbd className="pointer-events-none absolute end-[0.3rem] top-[0.3rem] hidden h-5 items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 select-none group-hover:bg-accent sm:flex">
|
|
34
34
|
<span className="text-xs">⌘</span>K
|
|
35
35
|
</kbd>
|
|
36
36
|
</Button>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export function SkipToMain() {
|
|
2
2
|
return (
|
|
3
3
|
<a
|
|
4
|
-
className={`fixed
|
|
4
|
+
className={`fixed start-44 z-999 -translate-y-52 bg-primary px-4 py-2 text-sm font-medium whitespace-nowrap text-primary-foreground opacity-95 shadow-sm transition hover:bg-primary/90 focus:translate-y-3 focus:transform focus-visible:ring-1 focus-visible:ring-ring`}
|
|
5
5
|
href="#content"
|
|
6
6
|
>
|
|
7
7
|
Skip to Main
|
|
@@ -61,7 +61,7 @@ function DialogContent({
|
|
|
61
61
|
{showCloseButton && (
|
|
62
62
|
<DialogPrimitive.Close
|
|
63
63
|
data-slot="dialog-close"
|
|
64
|
-
className="absolute
|
|
64
|
+
className="absolute end-4 top-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-open:bg-accent data-open:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
65
65
|
>
|
|
66
66
|
<XIcon />
|
|
67
67
|
<span className="sr-only">Close</span>
|
|
@@ -97,7 +97,7 @@ function DropdownMenuCheckboxItem({
|
|
|
97
97
|
checked={checked}
|
|
98
98
|
{...props}
|
|
99
99
|
>
|
|
100
|
-
<span className="pointer-events-none absolute
|
|
100
|
+
<span className="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
|
|
101
101
|
<DropdownMenuPrimitive.CheckboxItemIndicator>
|
|
102
102
|
<CheckIcon className="size-4" />
|
|
103
103
|
</DropdownMenuPrimitive.CheckboxItemIndicator>
|
|
@@ -127,7 +127,7 @@ function DropdownMenuRadioItem({
|
|
|
127
127
|
)}
|
|
128
128
|
{...props}
|
|
129
129
|
>
|
|
130
|
-
<span className="pointer-events-none absolute
|
|
130
|
+
<span className="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
|
|
131
131
|
<DropdownMenuPrimitive.RadioItemIndicator>
|
|
132
132
|
<CircleIcon className="size-2 fill-current" />
|
|
133
133
|
</DropdownMenuPrimitive.RadioItemIndicator>
|
|
@@ -143,7 +143,7 @@ function SelectItem({
|
|
|
143
143
|
)}
|
|
144
144
|
{...props}
|
|
145
145
|
>
|
|
146
|
-
<span className="absolute
|
|
146
|
+
<span className="absolute end-2 flex size-3.5 items-center justify-center">
|
|
147
147
|
<SelectPrimitive.ItemIndicator>
|
|
148
148
|
<CheckIcon className="size-4" />
|
|
149
149
|
</SelectPrimitive.ItemIndicator>
|
|
@@ -52,9 +52,9 @@ function SheetContent({
|
|
|
52
52
|
className={cn(
|
|
53
53
|
"fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-closed:animate-out data-closed:duration-300 data-open:animate-in data-open:duration-500",
|
|
54
54
|
side === "right" &&
|
|
55
|
-
"inset-y-0
|
|
55
|
+
"inset-y-0 end-0 h-full w-3/4 border-s data-closed:slide-out-to-end data-open:slide-in-from-end sm:max-w-sm",
|
|
56
56
|
side === "left" &&
|
|
57
|
-
"inset-y-0
|
|
57
|
+
"inset-y-0 start-0 h-full w-3/4 border-e data-closed:slide-out-to-start data-open:slide-in-from-start sm:max-w-sm",
|
|
58
58
|
side === "top" &&
|
|
59
59
|
"inset-x-0 top-0 h-auto border-b data-closed:slide-out-to-top data-open:slide-in-from-top",
|
|
60
60
|
side === "bottom" &&
|
|
@@ -64,7 +64,7 @@ function SheetContent({
|
|
|
64
64
|
{...props}
|
|
65
65
|
>
|
|
66
66
|
{children}
|
|
67
|
-
<SheetPrimitive.Close className="absolute
|
|
67
|
+
<SheetPrimitive.Close className="absolute end-4 top-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-open:bg-secondary">
|
|
68
68
|
<XIcon className="size-4" />
|
|
69
69
|
<span className="sr-only">Close</span>
|
|
70
70
|
</SheetPrimitive.Close>
|
|
@@ -227,8 +227,8 @@ function Sidebar({
|
|
|
227
227
|
className={cn(
|
|
228
228
|
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[inset-inline,width] duration-200 ease-linear md:flex",
|
|
229
229
|
side === "left"
|
|
230
|
-
? "
|
|
231
|
-
: "
|
|
230
|
+
? "start-0 group-data-[collapsible=offcanvas]:-start-[calc(var(--sidebar-width))]"
|
|
231
|
+
: "end-0 group-data-[collapsible=offcanvas]:-end-[calc(var(--sidebar-width))]",
|
|
232
232
|
// Adjust the padding for floating and inset variants.
|
|
233
233
|
variant === "floating" || variant === "inset"
|
|
234
234
|
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
|
@@ -283,12 +283,12 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
|
|
283
283
|
onClick={toggleSidebar}
|
|
284
284
|
title="Toggle Sidebar"
|
|
285
285
|
className={cn(
|
|
286
|
-
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-
|
|
286
|
+
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-end-4 group-data-[side=right]:start-0 after:absolute after:inset-y-0 after:start-1/2 after:w-0.5 hover:after:bg-sidebar-border sm:flex",
|
|
287
287
|
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
|
|
288
288
|
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
|
289
289
|
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:start-full hover:group-data-[collapsible=offcanvas]:bg-sidebar",
|
|
290
|
-
"[[data-side=left][data-collapsible=offcanvas]_&]:-
|
|
291
|
-
"[[data-side=right][data-collapsible=offcanvas]_&]:-
|
|
290
|
+
"[[data-side=left][data-collapsible=offcanvas]_&]:-end-2",
|
|
291
|
+
"[[data-side=right][data-collapsible=offcanvas]_&]:-start-2",
|
|
292
292
|
|
|
293
293
|
// RTL support
|
|
294
294
|
"rtl:translate-x-1/2",
|
|
@@ -428,7 +428,7 @@ function SidebarGroupAction({
|
|
|
428
428
|
"data-slot": "sidebar-group-action",
|
|
429
429
|
"data-sidebar": "group-action",
|
|
430
430
|
className: cn(
|
|
431
|
-
"absolute
|
|
431
|
+
"absolute end-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
432
432
|
// Increases the hit area of the button on mobile.
|
|
433
433
|
"after:absolute after:-inset-2 md:after:hidden",
|
|
434
434
|
"group-data-[collapsible=icon]:hidden",
|
|
@@ -553,7 +553,7 @@ function SidebarMenuAction({
|
|
|
553
553
|
"data-slot": "sidebar-menu-action",
|
|
554
554
|
"data-sidebar": "menu-action",
|
|
555
555
|
className: cn(
|
|
556
|
-
"absolute
|
|
556
|
+
"absolute end-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform peer-hover/menu-button:text-sidebar-accent-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
557
557
|
// Increases the hit area of the button on mobile.
|
|
558
558
|
"after:absolute after:-inset-2 md:after:hidden",
|
|
559
559
|
"peer-data-[size=sm]/menu-button:top-1",
|
|
@@ -574,7 +574,7 @@ function SidebarMenuBadge({ className, ...props }: React.ComponentProps<"div">)
|
|
|
574
574
|
data-slot="sidebar-menu-badge"
|
|
575
575
|
data-sidebar="menu-badge"
|
|
576
576
|
className={cn(
|
|
577
|
-
"pointer-events-none absolute
|
|
577
|
+
"pointer-events-none absolute end-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none",
|
|
578
578
|
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
|
|
579
579
|
"peer-data-[size=sm]/menu-button:top-1",
|
|
580
580
|
"peer-data-[size=default]/menu-button:top-1.5",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { Glob } from "bun";
|
|
4
|
+
|
|
5
|
+
// Модули собирают CSS через bun-plugin-tailwind со встроенным Tailwind 4.1: утилит 4.2+ там нет,
|
|
6
|
+
// класс молча не генерируется (Sheet side="right" уезжал влево). Логические отступы — только start-/end-.
|
|
7
|
+
const TAILWIND_42_ONLY = /\binset-(?:s|e|bs|be)-/;
|
|
8
|
+
|
|
9
|
+
const SRC = new URL(".", import.meta.url).pathname;
|
|
10
|
+
const SELF = new URL(import.meta.url).pathname;
|
|
11
|
+
|
|
12
|
+
describe("исходники кита: только утилиты Tailwind 4.1", () => {
|
|
13
|
+
it("нет inset-s-/inset-e-/inset-bs-/inset-be- (вместо них start-/end-/top-/bottom-)", async () => {
|
|
14
|
+
const files = await Array.fromAsync(
|
|
15
|
+
new Glob("**/*.{ts,tsx}").scan({ cwd: SRC, absolute: true })
|
|
16
|
+
);
|
|
17
|
+
expect(files.some((f) => f.endsWith("components/ui/sheet.tsx"))).toBe(true);
|
|
18
|
+
|
|
19
|
+
const violations: string[] = [];
|
|
20
|
+
for (const file of files) {
|
|
21
|
+
if (file === SELF) continue;
|
|
22
|
+
const lines = (await Bun.file(file).text()).split("\n");
|
|
23
|
+
lines.forEach((line, i) => {
|
|
24
|
+
const m = line.match(TAILWIND_42_ONLY);
|
|
25
|
+
if (m) violations.push(`${file.slice(SRC.length)}:${i + 1}: ${m[0]}`);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
expect(violations).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
});
|