@adea-ai/ui 0.83.1 → 0.84.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.
- package/dist/components/layout/sidebar-nav/index.d.ts +1 -0
- package/dist/components/layout/sidebar-nav/index.d.ts.map +1 -1
- package/dist/components/layout/sidebar-nav/index.js +2 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.d.ts +21 -0
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.d.ts.map +1 -0
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.js +84 -0
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.js.map +1 -0
- package/dist/index.js +2 -1
- package/dist/lint/index.d.ts +1 -0
- package/dist/lint/index.d.ts.map +1 -1
- package/dist/lint.js +171 -59
- package/dist/lint.js.map +1 -1
- package/dist/r/registry.json +6 -0
- package/dist/r/sidebar-nav.json +6 -0
- package/dist/r/src/components/layout/sidebar-nav/index.ts +4 -0
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx +59 -0
- package/package.json +1 -1
- package/registry.json +6 -0
- package/src/components/layout/sidebar-nav/index.ts +4 -0
- package/src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx +59 -0
- package/src/lint/index.ts +168 -19
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { SidebarNav, SidebarNavButton, SidebarNavContent, SidebarNavFooter, SidebarNavHeader, SidebarNavItem, SidebarNavSection, SidebarNavTitle, sidebarNavItemClass, sidebarNavItemStateClass, type SidebarNavItemProps, type SidebarNavProps, type SidebarNavTitleProps, } from './sidebar-nav';
|
|
2
|
+
export { SidebarNavResizeHandle, type SidebarNavResizeHandleProps, } from './sidebar-nav-resize-handle';
|
|
2
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/sidebar-nav/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/sidebar-nav/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,sBAAsB,EACtB,KAAK,2BAA2B,GACjC,MAAM,6BAA6B,CAAA"}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { SidebarNav, SidebarNavButton, SidebarNavContent, SidebarNavFooter, SidebarNavHeader, SidebarNavItem, SidebarNavSection, SidebarNavTitle, sidebarNavItemClass, sidebarNavItemStateClass } from "./sidebar-nav.js";
|
|
2
|
-
|
|
2
|
+
import { SidebarNavResizeHandle } from "./sidebar-nav-resize-handle.js";
|
|
3
|
+
export { SidebarNav, SidebarNavButton, SidebarNavContent, SidebarNavFooter, SidebarNavHeader, SidebarNavItem, SidebarNavResizeHandle, SidebarNavSection, SidebarNavTitle, sidebarNavItemClass, sidebarNavItemStateClass };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type SidebarNavResizeHandleProps = {
|
|
2
|
+
value: number;
|
|
3
|
+
minimum: number;
|
|
4
|
+
maximum: number;
|
|
5
|
+
step?: number;
|
|
6
|
+
label?: string;
|
|
7
|
+
controls?: string;
|
|
8
|
+
class?: string;
|
|
9
|
+
onChange(value: number): void;
|
|
10
|
+
onCommit?(value: number): void;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Resize a contextual sidebar without making its host recreate a separator
|
|
14
|
+
* widget. Corvu owns pointer capture, cancellation, constraints, keyboard
|
|
15
|
+
* movement and cleanup. Its two invisible panels provide a bounded pixel
|
|
16
|
+
* ruler; the host retains the real pane layout and preference persistence.
|
|
17
|
+
* Place this inside the positioned sidebar. The ruler can extend over the
|
|
18
|
+
* adjacent content, but only its handle participates in pointer hit testing.
|
|
19
|
+
*/
|
|
20
|
+
export declare function SidebarNavResizeHandle(props: SidebarNavResizeHandleProps): import("solid-js").JSX.Element;
|
|
21
|
+
//# sourceMappingURL=sidebar-nav-resize-handle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidebar-nav-resize-handle.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,2BAA2B,GAAG;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,kCAmCxE"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { cn } from "../../../lib/utils.js";
|
|
2
|
+
import { createComponent } from "solid-js/web";
|
|
3
|
+
import Resizable from "@corvu/resizable";
|
|
4
|
+
//#region src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx
|
|
5
|
+
/**
|
|
6
|
+
* Resize a contextual sidebar without making its host recreate a separator
|
|
7
|
+
* widget. Corvu owns pointer capture, cancellation, constraints, keyboard
|
|
8
|
+
* movement and cleanup. Its two invisible panels provide a bounded pixel
|
|
9
|
+
* ruler; the host retains the real pane layout and preference persistence.
|
|
10
|
+
* Place this inside the positioned sidebar. The ruler can extend over the
|
|
11
|
+
* adjacent content, but only its handle participates in pointer hit testing.
|
|
12
|
+
*/
|
|
13
|
+
function SidebarNavResizeHandle(props) {
|
|
14
|
+
const maximum = () => Math.max(1, props.maximum);
|
|
15
|
+
const minimum = () => Math.min(maximum(), Math.max(0, props.minimum));
|
|
16
|
+
const value = () => Math.min(maximum(), Math.max(minimum(), props.value));
|
|
17
|
+
const commit = () => props.onCommit?.(value());
|
|
18
|
+
return createComponent(Resizable, {
|
|
19
|
+
orientation: "horizontal",
|
|
20
|
+
get sizes() {
|
|
21
|
+
return [value() / maximum(), 1 - value() / maximum()];
|
|
22
|
+
},
|
|
23
|
+
get keyboardDelta() {
|
|
24
|
+
return `${props.step ?? 16}px`;
|
|
25
|
+
},
|
|
26
|
+
get style() {
|
|
27
|
+
return { width: `${maximum()}px` };
|
|
28
|
+
},
|
|
29
|
+
get ["class"]() {
|
|
30
|
+
return cn("pointer-events-none absolute inset-y-0 start-0 flex", props.class);
|
|
31
|
+
},
|
|
32
|
+
onSizesChange: (sizes) => {
|
|
33
|
+
const next = Math.round((sizes[0] ?? 0) * maximum());
|
|
34
|
+
if (next !== value()) props.onChange(Math.min(maximum(), Math.max(minimum(), next)));
|
|
35
|
+
},
|
|
36
|
+
get children() {
|
|
37
|
+
return [
|
|
38
|
+
createComponent(Resizable.Panel, {
|
|
39
|
+
get minSize() {
|
|
40
|
+
return minimum() / maximum();
|
|
41
|
+
},
|
|
42
|
+
maxSize: 1,
|
|
43
|
+
"aria-hidden": "true"
|
|
44
|
+
}),
|
|
45
|
+
createComponent(Resizable.Handle, {
|
|
46
|
+
get ["aria-label"]() {
|
|
47
|
+
return props.label ?? "Resize navigation";
|
|
48
|
+
},
|
|
49
|
+
get ["aria-controls"]() {
|
|
50
|
+
return props.controls;
|
|
51
|
+
},
|
|
52
|
+
"aria-orientation": "vertical",
|
|
53
|
+
get ["aria-valuemin"]() {
|
|
54
|
+
return minimum();
|
|
55
|
+
},
|
|
56
|
+
get ["aria-valuemax"]() {
|
|
57
|
+
return maximum();
|
|
58
|
+
},
|
|
59
|
+
get ["aria-valuenow"]() {
|
|
60
|
+
return value();
|
|
61
|
+
},
|
|
62
|
+
onHandleDragEnd: commit,
|
|
63
|
+
onKeyUp: (event) => {
|
|
64
|
+
if ([
|
|
65
|
+
"ArrowLeft",
|
|
66
|
+
"ArrowRight",
|
|
67
|
+
"Home",
|
|
68
|
+
"End"
|
|
69
|
+
].includes(event.key)) commit();
|
|
70
|
+
},
|
|
71
|
+
"class": "pointer-events-auto relative z-(--z-docked) w-px -translate-x-full shrink-0 bg-transparent after:absolute after:inset-y-0 after:-inset-x-1 after:w-3 hover:bg-primary focus-visible:bg-primary focus-visible:ring-3 focus-visible:ring-primary-subtle focus-visible:outline-none"
|
|
72
|
+
}),
|
|
73
|
+
createComponent(Resizable.Panel, {
|
|
74
|
+
minSize: 0,
|
|
75
|
+
"aria-hidden": "true"
|
|
76
|
+
})
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { SidebarNavResizeHandle };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=sidebar-nav-resize-handle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidebar-nav-resize-handle.js","names":["Resizable","cn","SidebarNavResizeHandleProps","value","minimum","maximum","step","label","controls","class","onChange","onCommit","SidebarNavResizeHandle","props","Math","max","min","commit","_$createComponent","orientation","sizes","keyboardDelta","style","width","onSizesChange","next","round","children","Panel","minSize","maxSize","Handle","aria-label","aria-controls","aria-valuemin","aria-valuemax","aria-valuenow","onHandleDragEnd","onKeyUp","event","includes","key","_$createComponent"],"sources":["../../../../src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx"],"sourcesContent":["import Resizable from '@corvu/resizable'\nimport { cn } from '#lib/utils'\n\nexport type SidebarNavResizeHandleProps = {\n value: number\n minimum: number\n maximum: number\n step?: number\n label?: string\n controls?: string\n class?: string\n onChange(value: number): void\n onCommit?(value: number): void\n}\n\n/**\n * Resize a contextual sidebar without making its host recreate a separator\n * widget. Corvu owns pointer capture, cancellation, constraints, keyboard\n * movement and cleanup. Its two invisible panels provide a bounded pixel\n * ruler; the host retains the real pane layout and preference persistence.\n * Place this inside the positioned sidebar. The ruler can extend over the\n * adjacent content, but only its handle participates in pointer hit testing.\n */\nexport function SidebarNavResizeHandle(props: SidebarNavResizeHandleProps) {\n const maximum = () => Math.max(1, props.maximum)\n const minimum = () => Math.min(maximum(), Math.max(0, props.minimum))\n const value = () => Math.min(maximum(), Math.max(minimum(), props.value))\n const commit = () => props.onCommit?.(value())\n\n return (\n <Resizable\n orientation=\"horizontal\"\n sizes={[value() / maximum(), 1 - value() / maximum()]}\n keyboardDelta={`${props.step ?? 16}px`}\n style={{ width: `${maximum()}px` }}\n class={cn('pointer-events-none absolute inset-y-0 start-0 flex', props.class)}\n onSizesChange={(sizes) => {\n const next = Math.round((sizes[0] ?? 0) * maximum())\n if (next !== value()) props.onChange(Math.min(maximum(), Math.max(minimum(), next)))\n }}\n >\n <Resizable.Panel minSize={minimum() / maximum()} maxSize={1} aria-hidden=\"true\" />\n <Resizable.Handle\n aria-label={props.label ?? 'Resize navigation'}\n aria-controls={props.controls}\n aria-orientation=\"vertical\"\n aria-valuemin={minimum()}\n aria-valuemax={maximum()}\n aria-valuenow={value()}\n onHandleDragEnd={commit}\n onKeyUp={(event) => {\n if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) commit()\n }}\n class=\"pointer-events-auto relative z-(--z-docked) w-px -translate-x-full shrink-0 bg-transparent after:absolute after:inset-y-0 after:-inset-x-1 after:w-3 hover:bg-primary focus-visible:bg-primary focus-visible:ring-3 focus-visible:ring-primary-subtle focus-visible:outline-none\"\n />\n <Resizable.Panel minSize={0} aria-hidden=\"true\" />\n </Resizable>\n )\n}\n"],"mappings":";;;;;;;;;;;;AAuBA,SAAgBY,uBAAuBC,OAAoC;CACzE,MAAMR,gBAAgBS,KAAKC,IAAI,GAAGF,MAAMR,OAAO;CAC/C,MAAMD,gBAAgBU,KAAKE,IAAIX,QAAQ,GAAGS,KAAKC,IAAI,GAAGF,MAAMT,OAAO,CAAC;CACpE,MAAMD,cAAcW,KAAKE,IAAIX,QAAQ,GAAGS,KAAKC,IAAIX,QAAQ,GAAGS,MAAMV,KAAK,CAAC;CACxE,MAAMc,eAAeJ,MAAMF,WAAWR,MAAM,CAAC;CAE7C,OAAAe,gBACGlB,WAAS;EACRmB,aAAW;EAAA,IACXC,QAAK;GAAA,OAAE,CAACjB,MAAM,IAAIE,QAAQ,GAAG,IAAIF,MAAM,IAAIE,QAAQ,CAAC;EAAC;EAAA,IACrDgB,gBAAa;GAAA,OAAE,GAAGR,MAAMP,QAAQ,GAAE;EAAI;EAAA,IACtCgB,QAAK;GAAA,OAAE,EAAEC,OAAO,GAAGlB,QAAQ,EAAC,IAAK;EAAC;EAAA,KAAA,WAAA;GAAA,OAC3BJ,GAAG,uDAAuDY,MAAMJ,KAAK;EAAC;EAC7Ee,gBAAgBJ,UAAU;GACxB,MAAMK,OAAOX,KAAKY,OAAON,MAAM,MAAM,KAAKf,QAAQ,CAAC;GACnD,IAAIoB,SAAStB,MAAM,GAAGU,MAAMH,SAASI,KAAKE,IAAIX,QAAQ,GAAGS,KAAKC,IAAIX,QAAQ,GAAGqB,IAAI,CAAC,CAAC;EACrF;EAAC,IAAAE,WAAA;GAAA,OAAA;IAAAT,gBAEAlB,UAAU4B,OAAK;KAAA,IAACC,UAAO;MAAA,OAAEzB,QAAQ,IAAIC,QAAQ;KAAC;KAAEyB,SAAS;KAAC,eAAA;IAAA,CAAA;IAAAZ,gBAC1DlB,UAAU+B,QAAM;KAAA,KAAA,gBAAA;MAAA,OACHlB,MAAMN,SAAS;KAAmB;KAAA,KAAA,mBAAA;MAAA,OAC/BM,MAAML;KAAQ;KAAA,oBAAA;KAAA,KAAA,mBAAA;MAAA,OAEdJ,QAAQ;KAAC;KAAA,KAAA,mBAAA;MAAA,OACTC,QAAQ;KAAC;KAAA,KAAA,mBAAA;MAAA,OACTF,MAAM;KAAC;KACtBkC,iBAAiBpB;KACjBqB,UAAUC,UAAU;MAClB,IAAI;OAAC;OAAa;OAAc;OAAQ;MAAK,CAAC,CAACC,SAASD,MAAME,GAAG,GAAGxB,OAAO;KAC7E;KAAC,SAAA;IAAA,CAAA;IAAAC,gBAGFlB,UAAU4B,OAAK;KAACC,SAAS;KAAC,eAAA;IAAA,CAAA;GAAA;EAAA;CAAA,CAAA;AAGjC"}
|
package/dist/index.js
CHANGED
|
@@ -71,6 +71,7 @@ import { Panel, PanelActions, PanelBody, PanelCard, PanelDescription, PanelFoote
|
|
|
71
71
|
import { useSideRail } from "./components/layout/side-rail/side-rail-context.js";
|
|
72
72
|
import { SideRail, SideRailButton, SideRailContent, SideRailFooter, SideRailHeader, SideRailItem, SideRailSection } from "./components/layout/side-rail/side-rail.js";
|
|
73
73
|
import { SidebarNav, SidebarNavButton, SidebarNavContent, SidebarNavFooter, SidebarNavHeader, SidebarNavItem, SidebarNavSection, SidebarNavTitle, sidebarNavItemClass, sidebarNavItemStateClass } from "./components/layout/sidebar-nav/sidebar-nav.js";
|
|
74
|
+
import { SidebarNavResizeHandle } from "./components/layout/sidebar-nav/sidebar-nav-resize-handle.js";
|
|
74
75
|
import { StatusBar, StatusBarItem, StatusBarSpacer } from "./components/layout/status-bar/status-bar.js";
|
|
75
76
|
import { TopBar, TopBarBreadcrumb, TopBarPill, TopBarSearch, TopBarSection, TopBarTitle } from "./components/layout/top-bar/top-bar.js";
|
|
76
77
|
import { BusySendButton } from "./components/conversation/busy-send-button.js";
|
|
@@ -99,4 +100,4 @@ import { formatBytes, formatReleaseDate, plainTextFromMarkdown } from "./lib/ver
|
|
|
99
100
|
import { UpdateDialog } from "./components/composites/update-dialog/update-dialog.js";
|
|
100
101
|
import { WorkspaceMark } from "./components/composites/workspace-mark/workspace-mark.js";
|
|
101
102
|
import { keyedRows } from "./lib/keyed-rows.js";
|
|
102
|
-
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, AccountMenu, ActionButton, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, AppShellBody, AppShellContent, AppShellMain, AppearanceEditor, AppearancePanel, AppearancePopover, AspectRatio, AttachmentCard, Avatar, AvatarFallback, AvatarImage, Badge, Board, BoardCardBody, BoardCardTitle, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator, BusySendButton, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, CalendarSurface, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChatComposer, Checkbox, CheckboxDescription, CheckboxLabel, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, ComboboxClear, ComboboxContent, ComboboxControl, ComboboxInput, ComboboxItem, ComboboxSection, ComboboxTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandLoading, CommandSeparator, CommandShortcut, ComposerAttachmentButton, ComposerHint, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, ConversationAvatar, ConversationSurface, DatePicker, DetailPanel, DetailPanelBody, DetailPanelField, DetailPanelHeader, DetailPanelSection, Dialog, DialogClose, DialogCloseButton, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffBlock, DiffSummary, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, EntityIcon, Field, FieldDescription, FieldError, FieldInput, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTextArea, HoverCard, HoverCardContent, HoverCardTrigger, InlineCode, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputOtp, InputOtpCaret, Item, ItemDescription, ItemEmpty, ItemGroup, ItemGroupEntry, ItemTitle, Kbd, KbdGroup, Label, ListGroup, ListRow, MAX_LAYOUT_DEPTH, MAX_LAYOUT_LEAVES, MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, MessageBody, MessageComposer, MessageDayDivider, MessageGroup, MessageRow, ModalDialog, Motion, NativeSelect, NavigationMenu, NavigationMenuContent, NavigationMenuGroup, NavigationMenuGroupLabel, NavigationMenuItem, NavigationMenuMenu, NavigationMenuSeparator, NavigationMenuTrigger, NavigationMenuViewport, OrbitItem, OrbitLayout, PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, Page, PageHeader, PageHeaderActions, PageHeaderContent, PageHeaderDescription, PageHeaderTitle, PageSection, Pagination, PaginationEllipsis, PaginationItem, PaginationNext, PaginationPrevious, PalettePreview, Panel, PanelActions, PanelBody, PanelCard, PanelDescription, PanelFooter, PanelHeader, PanelPlaceholder, PanelTitle, PanelToolbar, Popover, PopoverAnchor, PopoverCloseButton, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger, Presence, Progress, PropertyList, PropertyRow, PropertyTerm, PropertyValue, RadioGroup, RadioGroupItem, ResizableHandle, ResizablePanel, ResizablePanelGroup, ScrollArea, Select, SelectChevronsUpDown, SelectContent, SelectItem, SelectLabel, SelectSection, SelectTrigger, SelectValue, Separator, SettingsField, SettingsPage, SettingsRow, SettingsSection, Sheet, SheetBody, SheetCloseButton, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, SideRail, SideRailButton, SideRailContent, SideRailFooter, SideRailHeader, SideRailItem, SideRailSection, SidebarNav, SidebarNavButton, SidebarNavContent, SidebarNavFooter, SidebarNavHeader, SidebarNavItem, SidebarNavSection, SidebarNavTitle, Skeleton, Slider, Spinner, SplitLayout, Stat, StatGroup, StatusBar, StatusBarItem, StatusBarSpacer, StatusChip, StatusList, Switch, SwitchDescription, SwitchLabel, TALL_CODE_BLOCK_PX, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableNumericCell, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ThemeMiniature, ThemeMiniatureSplit, ThemePicker, ThemePreview, ThemeProvider, ThemeSwatch, ThemeToggle, ThreadPanel, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TopBar, TopBarBreadcrumb, TopBarPill, TopBarSearch, TopBarSection, TopBarTitle, TranscriptComposition, UpdateDialog, VirtualWindow, WorkspaceMark, accentPresets, alertVariants, allTokens, badgeVariants, boardCardTitleVariants, builtinThemes, buttonVariants, closePane, cn, colorTokens, computeLayoutFrames, contrastRatio, controlInteractive, controlSize, controlSizeIcon, controlSizes, countLeaves, countLines, createLayoutState, cva, defaultDarkThemeId, defaultLightThemeId, defaultThemeSelection, densityTokens, designTokens, diffLineVariants, diffStats, diffTargetPath, elevationTokens, entityIconVariants, expandAll, findTokenRanges, focusPane, fontOptions, formatBytes, formatReleaseDate, formatToken, initialsFrom, inlineScriptLiteral, isPasteBlock, itemVariants, keyedRows, layoutDepth, listLeaves, monogram, motionTokens, movePane, neighborLeaf, nextSeq, normalizeLayout, parseDiff, plainTextFromMarkdown, pruneBlocks, radiusTokens, recollapsePastes, remapCarriedBlocks, resizeSplit, resolvedDensity, sheetVariants, shouldCollapse, sidebarNavItemClass, sidebarNavItemStateClass, splitPane, statusDotVariants, stripTrailingBlankLines, surfaceInteractive, swapPanes, themeById, themeCssVariables, themeFamilies, themeScript, themesForAppearance, toast, toaster, toggleVariants, tokenRangeAt, transcriptDisclosureKey, typographyTokens, undoClosePane, undocumentedTokenAliases, useSideRail, useTheme, validateTheme, validateThemeRegistry, zIndexTokens };
|
|
103
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, AccountMenu, ActionButton, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, AppShellBody, AppShellContent, AppShellMain, AppearanceEditor, AppearancePanel, AppearancePopover, AspectRatio, AttachmentCard, Avatar, AvatarFallback, AvatarImage, Badge, Board, BoardCardBody, BoardCardTitle, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator, BusySendButton, Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText, CalendarSurface, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChatComposer, Checkbox, CheckboxDescription, CheckboxLabel, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, ComboboxClear, ComboboxContent, ComboboxControl, ComboboxInput, ComboboxItem, ComboboxSection, ComboboxTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandLoading, CommandSeparator, CommandShortcut, ComposerAttachmentButton, ComposerHint, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, ConversationAvatar, ConversationSurface, DatePicker, DetailPanel, DetailPanelBody, DetailPanelField, DetailPanelHeader, DetailPanelSection, Dialog, DialogClose, DialogCloseButton, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffBlock, DiffSummary, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, EntityIcon, Field, FieldDescription, FieldError, FieldInput, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTextArea, HoverCard, HoverCardContent, HoverCardTrigger, InlineCode, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputOtp, InputOtpCaret, Item, ItemDescription, ItemEmpty, ItemGroup, ItemGroupEntry, ItemTitle, Kbd, KbdGroup, Label, ListGroup, ListRow, MAX_LAYOUT_DEPTH, MAX_LAYOUT_LEAVES, MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, MessageBody, MessageComposer, MessageDayDivider, MessageGroup, MessageRow, ModalDialog, Motion, NativeSelect, NavigationMenu, NavigationMenuContent, NavigationMenuGroup, NavigationMenuGroupLabel, NavigationMenuItem, NavigationMenuMenu, NavigationMenuSeparator, NavigationMenuTrigger, NavigationMenuViewport, OrbitItem, OrbitLayout, PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, Page, PageHeader, PageHeaderActions, PageHeaderContent, PageHeaderDescription, PageHeaderTitle, PageSection, Pagination, PaginationEllipsis, PaginationItem, PaginationNext, PaginationPrevious, PalettePreview, Panel, PanelActions, PanelBody, PanelCard, PanelDescription, PanelFooter, PanelHeader, PanelPlaceholder, PanelTitle, PanelToolbar, Popover, PopoverAnchor, PopoverCloseButton, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger, Presence, Progress, PropertyList, PropertyRow, PropertyTerm, PropertyValue, RadioGroup, RadioGroupItem, ResizableHandle, ResizablePanel, ResizablePanelGroup, ScrollArea, Select, SelectChevronsUpDown, SelectContent, SelectItem, SelectLabel, SelectSection, SelectTrigger, SelectValue, Separator, SettingsField, SettingsPage, SettingsRow, SettingsSection, Sheet, SheetBody, SheetCloseButton, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, SideRail, SideRailButton, SideRailContent, SideRailFooter, SideRailHeader, SideRailItem, SideRailSection, SidebarNav, SidebarNavButton, SidebarNavContent, SidebarNavFooter, SidebarNavHeader, SidebarNavItem, SidebarNavResizeHandle, SidebarNavSection, SidebarNavTitle, Skeleton, Slider, Spinner, SplitLayout, Stat, StatGroup, StatusBar, StatusBarItem, StatusBarSpacer, StatusChip, StatusList, Switch, SwitchDescription, SwitchLabel, TALL_CODE_BLOCK_PX, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableNumericCell, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ThemeMiniature, ThemeMiniatureSplit, ThemePicker, ThemePreview, ThemeProvider, ThemeSwatch, ThemeToggle, ThreadPanel, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TopBar, TopBarBreadcrumb, TopBarPill, TopBarSearch, TopBarSection, TopBarTitle, TranscriptComposition, UpdateDialog, VirtualWindow, WorkspaceMark, accentPresets, alertVariants, allTokens, badgeVariants, boardCardTitleVariants, builtinThemes, buttonVariants, closePane, cn, colorTokens, computeLayoutFrames, contrastRatio, controlInteractive, controlSize, controlSizeIcon, controlSizes, countLeaves, countLines, createLayoutState, cva, defaultDarkThemeId, defaultLightThemeId, defaultThemeSelection, densityTokens, designTokens, diffLineVariants, diffStats, diffTargetPath, elevationTokens, entityIconVariants, expandAll, findTokenRanges, focusPane, fontOptions, formatBytes, formatReleaseDate, formatToken, initialsFrom, inlineScriptLiteral, isPasteBlock, itemVariants, keyedRows, layoutDepth, listLeaves, monogram, motionTokens, movePane, neighborLeaf, nextSeq, normalizeLayout, parseDiff, plainTextFromMarkdown, pruneBlocks, radiusTokens, recollapsePastes, remapCarriedBlocks, resizeSplit, resolvedDensity, sheetVariants, shouldCollapse, sidebarNavItemClass, sidebarNavItemStateClass, splitPane, statusDotVariants, stripTrailingBlankLines, surfaceInteractive, swapPanes, themeById, themeCssVariables, themeFamilies, themeScript, themesForAppearance, toast, toaster, toggleVariants, tokenRangeAt, transcriptDisclosureKey, typographyTokens, undoClosePane, undocumentedTokenAliases, useSideRail, useTheme, validateTheme, validateThemeRegistry, zIndexTokens };
|
package/dist/lint/index.d.ts
CHANGED
package/dist/lint/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lint/index.ts"],"names":[],"mappings":"AAmBA,KAAK,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAa/E,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS,CAAA;QACf,IAAI,EAAE;YAAE,WAAW,EAAE,MAAM,CAAA;SAAE,CAAA;QAC7B,MAAM,EAAE,IAAI,EAAE,CAAA;QACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACjC,CAAA;IACD,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC,CAAA;CACtE,CAAA;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,OAAO,EAAE,CAAA;IACnB,MAAM,EAAE,CAAC,UAAU,EAAE;QACnB,IAAI,EAAE,OAAO,CAAA;QACb,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;KAC5B,KAAK,IAAI,CAAA;CACX;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lint/index.ts"],"names":[],"mappings":"AAmBA,KAAK,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAa/E,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS,CAAA;QACf,IAAI,EAAE;YAAE,WAAW,EAAE,MAAM,CAAA;SAAE,CAAA;QAC7B,MAAM,EAAE,IAAI,EAAE,CAAA;QACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACjC,CAAA;IACD,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC,CAAA;CACtE,CAAA;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,OAAO,EAAE,CAAA;IACnB,MAAM,EAAE,CAAC,UAAU,EAAE;QACnB,IAAI,EAAE,OAAO,CAAA;QACb,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;KAC5B,KAAK,IAAI,CAAA;CACX;AAgkBD,QAAA,MAAM,MAAM;;;;;;;;;;;;CAUX,CAAA;AAED,eAAe,MAAM,CAAA"}
|
package/dist/lint.js
CHANGED
|
@@ -45,6 +45,18 @@ var objectHasStaticProperty = (node, name, seen = /* @__PURE__ */ new Set()) =>
|
|
|
45
45
|
return false;
|
|
46
46
|
});
|
|
47
47
|
};
|
|
48
|
+
/** Returns literal values for a statically named property in an object spread. */
|
|
49
|
+
var staticObjectPropertyValues = (node, name, seen = /* @__PURE__ */ new Set()) => {
|
|
50
|
+
if (!node || seen.has(node)) return [];
|
|
51
|
+
seen.add(node);
|
|
52
|
+
if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSNonNullExpression") return staticObjectPropertyValues(node.expression, name, seen);
|
|
53
|
+
if (node.type !== "ObjectExpression") return [];
|
|
54
|
+
return node.properties.flatMap((property) => {
|
|
55
|
+
if (property.type === "Property") return staticPropertyKey(property.key, property.computed === true) === name ? [property.value] : [];
|
|
56
|
+
if (property.type === "SpreadElement") return staticObjectPropertyValues(property.argument, name, seen);
|
|
57
|
+
return [];
|
|
58
|
+
});
|
|
59
|
+
};
|
|
48
60
|
var noInlineStyles = {
|
|
49
61
|
meta: {
|
|
50
62
|
type: "problem",
|
|
@@ -251,20 +263,139 @@ var staticStrings = (node) => {
|
|
|
251
263
|
};
|
|
252
264
|
var roleOf = (node) => {
|
|
253
265
|
for (const attribute of node.attributes ?? []) {
|
|
266
|
+
if (attribute.type === "JSXSpreadAttribute") {
|
|
267
|
+
const role = staticObjectPropertyValues(attribute.argument, "role").flatMap((value) => staticStrings(value)).flatMap((value) => value.split(/\s+/)).find((value) => value in ROLE_PRIMITIVES);
|
|
268
|
+
if (role) return role;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
254
271
|
if (attribute.type !== "JSXAttribute" || attribute.name?.name !== "role") continue;
|
|
255
272
|
return staticStrings(attribute.value).flatMap((role) => role.split(/\s+/)).find((role) => role in ROLE_PRIMITIVES) ?? null;
|
|
256
273
|
}
|
|
257
274
|
return null;
|
|
258
275
|
};
|
|
259
276
|
/** Whether the element carries the attribute, by name. */
|
|
260
|
-
var has = (node, name) => (node.attributes ?? []).some((attribute) => attribute.type === "JSXAttribute" && (attribute.name?.type === "JSXNamespacedName" ? `${attribute.name.namespace.name}:${attribute.name.name.name}` : attribute.name?.name) === name);
|
|
261
|
-
var
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (
|
|
265
|
-
if (value?.type === "UnaryExpression" && value.operator === "-" && value.argument?.type === "Literal") return !(Number(value.argument.value) > 0);
|
|
277
|
+
var has = (node, name) => (node.attributes ?? []).some((attribute) => attribute.type === "JSXAttribute" && (attribute.name?.type === "JSXNamespacedName" ? `${attribute.name.namespace.name}:${attribute.name.name.name}` : attribute.name?.name) === name || attribute.type === "JSXSpreadAttribute" && objectHasStaticProperty(attribute.argument, name));
|
|
278
|
+
var tabStopValue = (value) => {
|
|
279
|
+
const expression = value?.type === "JSXExpressionContainer" ? value.expression : value;
|
|
280
|
+
if (expression?.type === "Literal") return !(Number(expression.value) < 0);
|
|
281
|
+
if (expression?.type === "UnaryExpression" && expression.operator === "-" && expression.argument?.type === "Literal") return !(Number(expression.argument.value) > 0);
|
|
266
282
|
return true;
|
|
283
|
+
};
|
|
284
|
+
var hasTabStop = (node) => (node.attributes ?? []).some((attribute) => {
|
|
285
|
+
if (attribute.type === "JSXAttribute" && ["tabIndex", "tabindex"].includes(attribute.name?.name)) return tabStopValue(attribute.value);
|
|
286
|
+
if (attribute.type === "JSXSpreadAttribute") return ["tabIndex", "tabindex"].some((name) => staticObjectPropertyValues(attribute.argument, name).some(tabStopValue));
|
|
287
|
+
return false;
|
|
288
|
+
});
|
|
289
|
+
var noInteractiveWrappers = {
|
|
290
|
+
meta: {
|
|
291
|
+
type: "problem",
|
|
292
|
+
docs: { description: "Generic elements dressed as interactive controls — an interactive role, a click handler or a tabindex on a layout element — re-implement a primitive by hand. Compose the real component." },
|
|
293
|
+
schema: [{
|
|
294
|
+
type: "object",
|
|
295
|
+
properties: {
|
|
296
|
+
allow: {
|
|
297
|
+
type: "array",
|
|
298
|
+
items: { type: "string" }
|
|
299
|
+
},
|
|
300
|
+
message: { type: "string" }
|
|
301
|
+
},
|
|
302
|
+
additionalProperties: false
|
|
303
|
+
}],
|
|
304
|
+
messages: {
|
|
305
|
+
roleWrapper: "A {{element}} with role=\"{{role}}\" hand-builds {{primitive}}: use {{primitive}} from {{path}}. The primitive brings keyboard activation, focus behaviour and ARIA wiring; the wrapper re-implements them badly.",
|
|
306
|
+
clickWrapper: "A click handler on {{element}} composes {{primitive}} by hand: use {{primitive}} from {{path}}. A div that reacts to clicks is a button that cannot be reached by keyboard.",
|
|
307
|
+
tabindexWrapper: "A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead."
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
create(context) {
|
|
311
|
+
const options = context.options?.[0] ?? {};
|
|
312
|
+
const allowed = new Set(options.allow ?? []);
|
|
313
|
+
return { JSXOpeningElement(node) {
|
|
314
|
+
if (node.name?.type !== "JSXIdentifier") return;
|
|
315
|
+
const element = node.name.name;
|
|
316
|
+
if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return;
|
|
317
|
+
if (element[0] === element[0]?.toUpperCase()) return;
|
|
318
|
+
const role = roleOf(node);
|
|
319
|
+
if (role && role in ROLE_PRIMITIVES) {
|
|
320
|
+
const target = ROLE_PRIMITIVES[role];
|
|
321
|
+
context.report({
|
|
322
|
+
node,
|
|
323
|
+
messageId: "roleWrapper",
|
|
324
|
+
data: {
|
|
325
|
+
element,
|
|
326
|
+
role,
|
|
327
|
+
primitive: target.primitive,
|
|
328
|
+
path: target.path
|
|
329
|
+
},
|
|
330
|
+
...options.message ? { message: options.message } : {}
|
|
331
|
+
});
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (GENERIC_ELEMENTS.has(element)) {
|
|
335
|
+
if ([...CLICK_HANDLERS].find((handler) => has(node, handler))) {
|
|
336
|
+
const target = ROLE_PRIMITIVES.button;
|
|
337
|
+
context.report({
|
|
338
|
+
node,
|
|
339
|
+
messageId: "clickWrapper",
|
|
340
|
+
data: {
|
|
341
|
+
element,
|
|
342
|
+
primitive: target.primitive,
|
|
343
|
+
path: target.path
|
|
344
|
+
},
|
|
345
|
+
...options.message ? { message: options.message } : {}
|
|
346
|
+
});
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (hasTabStop(node)) context.report({
|
|
350
|
+
node,
|
|
351
|
+
messageId: "tabindexWrapper",
|
|
352
|
+
data: { element },
|
|
353
|
+
...options.message ? { message: options.message } : {}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
} };
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
var BUTTON_MODULE = "@adea-ai/ui/components/ui/button";
|
|
360
|
+
var ACTION_BUTTON_MODULE = "@adea-ai/ui/components/composites/action-button";
|
|
361
|
+
var ICON_BUTTON_SIZES = /* @__PURE__ */ new Set([
|
|
362
|
+
"icon-2xs",
|
|
363
|
+
"icon-xs",
|
|
364
|
+
"icon-sm",
|
|
365
|
+
"icon-md",
|
|
366
|
+
"icon-lg",
|
|
367
|
+
"icon-xl"
|
|
368
|
+
]);
|
|
369
|
+
var staticJsxValues = (value) => value?.type === "JSXExpressionContainer" ? [value.expression] : value ? [value] : [];
|
|
370
|
+
var attributeValues = (node, name) => {
|
|
371
|
+
const values = [];
|
|
372
|
+
for (const attribute of node.attributes ?? []) {
|
|
373
|
+
if (attribute.type === "JSXAttribute" && attribute.name?.name === name) values.splice(0, values.length, ...staticJsxValues(attribute.value));
|
|
374
|
+
if (attribute.type === "JSXSpreadAttribute") {
|
|
375
|
+
const spreadValues = staticObjectPropertyValues(attribute.argument, name);
|
|
376
|
+
if (spreadValues.length > 0) values.splice(0, values.length, spreadValues.at(-1));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return values;
|
|
380
|
+
};
|
|
381
|
+
var staticStringValue = (node) => {
|
|
382
|
+
if (node?.type === "Literal" && typeof node.value === "string") return node.value;
|
|
383
|
+
if (node?.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;
|
|
384
|
+
return null;
|
|
385
|
+
};
|
|
386
|
+
var isIconButtonSize = (node) => attributeValues(node, "size").some((value) => {
|
|
387
|
+
const size = staticStringValue(value);
|
|
388
|
+
return size !== null && ICON_BUTTON_SIZES.has(size);
|
|
267
389
|
});
|
|
390
|
+
var actionTooltipStatus = (node) => {
|
|
391
|
+
const values = attributeValues(node, "tooltip");
|
|
392
|
+
if (values.length === 0) return "missing";
|
|
393
|
+
const strings = values.map(staticStringValue);
|
|
394
|
+
if (strings.some((value) => value !== null && value.trim().length > 0)) return "valid";
|
|
395
|
+
if (strings.every((value) => value !== null)) return "blank";
|
|
396
|
+
return "unknown";
|
|
397
|
+
};
|
|
398
|
+
var isImportedComponent = (identifier, imports) => identifier?.type === "Identifier" && imports.has(identifier.name);
|
|
268
399
|
var plugin = {
|
|
269
400
|
meta: { name: "adea" },
|
|
270
401
|
rules: {
|
|
@@ -325,74 +456,55 @@ var plugin = {
|
|
|
325
456
|
};
|
|
326
457
|
}
|
|
327
458
|
},
|
|
328
|
-
"no-interactive-wrappers":
|
|
459
|
+
"no-interactive-wrappers": noInteractiveWrappers,
|
|
460
|
+
"require-action-button-tooltip": {
|
|
329
461
|
meta: {
|
|
330
462
|
type: "problem",
|
|
331
|
-
docs: { description: "
|
|
332
|
-
schema: [
|
|
333
|
-
type: "object",
|
|
334
|
-
properties: {
|
|
335
|
-
allow: {
|
|
336
|
-
type: "array",
|
|
337
|
-
items: { type: "string" }
|
|
338
|
-
},
|
|
339
|
-
message: { type: "string" }
|
|
340
|
-
},
|
|
341
|
-
additionalProperties: false
|
|
342
|
-
}],
|
|
463
|
+
docs: { description: "Consumer icon-size shared Button actions use ActionButton with a nonblank tooltip; dynamic values remain a review boundary." },
|
|
464
|
+
schema: [],
|
|
343
465
|
messages: {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
tabindexWrapper: "A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead."
|
|
466
|
+
useActionButton: "Icon-size Button actions use ActionButton with a nonblank tooltip. Keep the control name in aria-label; the tooltip explains the action.",
|
|
467
|
+
missingTooltip: "An icon-size ActionButton needs a supplied nonblank tooltip. Keep its accessible name in aria-label as well."
|
|
347
468
|
}
|
|
348
469
|
},
|
|
349
470
|
create(context) {
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
context.report({
|
|
361
|
-
node,
|
|
362
|
-
messageId: "roleWrapper",
|
|
363
|
-
data: {
|
|
364
|
-
element,
|
|
365
|
-
role,
|
|
366
|
-
primitive: target.primitive,
|
|
367
|
-
path: target.path
|
|
368
|
-
},
|
|
369
|
-
...options.message ? { message: options.message } : {}
|
|
370
|
-
});
|
|
371
|
-
return;
|
|
471
|
+
const buttons = /* @__PURE__ */ new Set();
|
|
472
|
+
const actionButtons = /* @__PURE__ */ new Set();
|
|
473
|
+
const recordImports = (node) => {
|
|
474
|
+
const source = node.source?.value;
|
|
475
|
+
const target = source === BUTTON_MODULE ? buttons : source === ACTION_BUTTON_MODULE ? actionButtons : null;
|
|
476
|
+
if (!target) return;
|
|
477
|
+
for (const specifier of node.specifiers ?? []) {
|
|
478
|
+
if (specifier.type !== "ImportSpecifier") continue;
|
|
479
|
+
const imported = specifier.imported?.name ?? specifier.imported?.value;
|
|
480
|
+
if (source === BUTTON_MODULE && imported === "Button" || source === ACTION_BUTTON_MODULE && imported === "ActionButton") target.add(specifier.local?.name);
|
|
372
481
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
482
|
+
};
|
|
483
|
+
return {
|
|
484
|
+
ImportDeclaration: recordImports,
|
|
485
|
+
JSXOpeningElement(node) {
|
|
486
|
+
if (!isIconButtonSize(node)) return;
|
|
487
|
+
const name = node.name?.type === "JSXIdentifier" ? node.name.name : null;
|
|
488
|
+
const isButton = name !== null && buttons.has(name);
|
|
489
|
+
const isActionButton = name !== null && actionButtons.has(name);
|
|
490
|
+
const asValues = attributeValues(node, "as");
|
|
491
|
+
const targetsButton = asValues.some((value) => isImportedComponent(value, buttons));
|
|
492
|
+
const targetsActionButton = asValues.some((value) => isImportedComponent(value, actionButtons));
|
|
493
|
+
if (isButton || targetsButton) {
|
|
376
494
|
context.report({
|
|
377
495
|
node,
|
|
378
|
-
messageId: "
|
|
379
|
-
data: {
|
|
380
|
-
element,
|
|
381
|
-
primitive: target.primitive,
|
|
382
|
-
path: target.path
|
|
383
|
-
},
|
|
384
|
-
...options.message ? { message: options.message } : {}
|
|
496
|
+
messageId: "useActionButton"
|
|
385
497
|
});
|
|
386
498
|
return;
|
|
387
499
|
}
|
|
388
|
-
if (
|
|
500
|
+
if (!isActionButton && !targetsActionButton) return;
|
|
501
|
+
const status = actionTooltipStatus(node);
|
|
502
|
+
if (status === "missing" || status === "blank") context.report({
|
|
389
503
|
node,
|
|
390
|
-
messageId: "
|
|
391
|
-
data: { element },
|
|
392
|
-
...options.message ? { message: options.message } : {}
|
|
504
|
+
messageId: "missingTooltip"
|
|
393
505
|
});
|
|
394
506
|
}
|
|
395
|
-
}
|
|
507
|
+
};
|
|
396
508
|
}
|
|
397
509
|
}
|
|
398
510
|
}
|
package/dist/lint.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint.js","names":[],"sources":["../src/lint/index.ts"],"sourcesContent":["/*\n * The design system's own oxlint plugin.\n *\n * The styling rules live in `@shadcn/lint`; what that plugin cannot know is which\n * *elements* the design system owns. This is the other half of the contract, for\n * the applications that consume the library: an interactive element this package\n * provides is not re-implemented at the application layer, and the primitive\n * libraries this package is built from are not imported around it.\n *\n * Loaded as an oxlint jsPlugin:\n *\n * // .oxlintrc.json\n * { \"jsPlugins\": [\"@adea-ai/ui/lint\"], \"rules\": { \"adea/no-raw-interactive-elements\": \"error\" } }\n *\n * Rules are errors by intent. A violation means an application composed its own\n * control out of raw markup — unreachable focus behaviour, an absent keyboard\n * story, a second implementation of a decision this package exists to make once.\n */\n\ntype Json = string | number | boolean | null | Json[] | { [key: string]: Json }\n\n/** The raw elements this package replaces, and the primitive that replaces them. */\nconst ELEMENT_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n input: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n textarea: { primitive: 'Textarea', path: '@adea-ai/ui/components/ui/textarea' },\n select: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n label: { primitive: 'Label', path: '@adea-ai/ui/components/ui/label' },\n })\n\ntype RuleModule = {\n meta: {\n type: 'problem'\n docs: { description: string }\n schema: Json[]\n messages: Record<string, string>\n }\n create: (context: LintContext) => Record<string, (node: any) => void>\n}\n\ninterface LintContext {\n options?: unknown[]\n report: (descriptor: {\n node: unknown\n messageId?: string\n message?: string\n data?: Record<string, Json>\n }) => void\n}\n\n/** Reads a static object-property key without evaluating a computed expression. */\nconst staticPropertyKey = (node: any, computed: boolean): string | null => {\n if (!computed && node?.type === 'Identifier') return node.name\n if (node?.type === 'Literal' && typeof node.value === 'string') return node.value\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw\n return null\n}\n\n/** Detects named props in a literal JSX spread, including nested literal spreads. */\nconst objectHasStaticProperty = (node: any, name: string, seen = new Set<unknown>()): boolean => {\n if (!node || seen.has(node)) return false\n seen.add(node)\n if (\n node.type === 'TSAsExpression' ||\n node.type === 'TSSatisfiesExpression' ||\n node.type === 'TSNonNullExpression'\n )\n return objectHasStaticProperty(node.expression, name, seen)\n if (node.type !== 'ObjectExpression') return false\n return node.properties.some((property: any) => {\n if (property.type === 'Property')\n return staticPropertyKey(property.key, property.computed === true) === name\n if (property.type === 'SpreadElement')\n return objectHasStaticProperty(property.argument, name, seen)\n return false\n })\n}\n\nconst noInlineStyles: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Consumer components cannot set appearance or layout with JSX style props; use shared variants and token-backed classes.',\n },\n schema: [],\n messages: {\n inlineStyle:\n 'JSX style attributes are not permitted, including layout values and CSS custom properties. Use shared variants and token-backed classes.',\n inlineStyleElement:\n 'A JSX style element bypasses the shared design system. Use shared variants and token-backed classes.',\n inlineStyleSpread:\n 'A JSX spread contains a style prop. Pass named shared component props instead of hiding inline styles in a spread.',\n },\n },\n create(context) {\n return {\n JSXAttribute(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'style')\n context.report({ node, messageId: 'inlineStyle' })\n },\n JSXOpeningElement(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'style')\n context.report({ node, messageId: 'inlineStyleElement' })\n },\n JSXSpreadAttribute(node: any) {\n if (objectHasStaticProperty(node.argument, 'style'))\n context.report({ node, messageId: 'inlineStyleSpread' })\n },\n }\n },\n}\n\nconst noClassList: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Solid classList props hide conditional classes from static design-system lint; use cn() with its object-key form.',\n },\n schema: [],\n messages: {\n classList:\n 'classList hides classes from design-system lint. Use cn() with its object-key form and statically readable class names.',\n classListSpread:\n 'A JSX spread contains classList. Use cn() with its object-key form instead of hiding classes in a spread.',\n },\n },\n create(context) {\n return {\n JSXAttribute(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'classList')\n context.report({ node, messageId: 'classList' })\n },\n JSXSpreadAttribute(node: any) {\n if (objectHasStaticProperty(node.argument, 'classList'))\n context.report({ node, messageId: 'classListSpread' })\n },\n }\n },\n}\n\ninterface RawElementOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst noRawInteractiveElements: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n rawElement:\n '{{element}} is provided by the design system: use {{primitive}} from {{path}}. A raw element has no keyboard story, no focus behaviour and no token styling — that is the work the primitive already did.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as RawElementOptions\n const allowed = new Set(options.allow ?? [])\n const emit = (node: unknown, element: string) => {\n const target = ELEMENT_PRIMITIVES[element]!\n context.report({\n node,\n messageId: 'rawElement',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return\n emit(node, element)\n },\n }\n },\n}\n\ninterface PrimitiveImportOptions {\n /** Package prefixes this rule reports. Defaults to the primitive libraries the ui package wraps. */\n packages?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([\n '@kobalte/core',\n '@corvu',\n 'cmdk-solid',\n '@ark-ui',\n '@zag-js',\n '@radix-ui',\n 'radix-ui',\n '@base-ui-components',\n '@base-ui',\n])\n\n/**\n * The interactive ARIA roles an application can dress a generic element in, and\n * the primitive that owns each role. `link` is deliberately absent: an `<a>` is\n * natively interactive and Button itself supports `as=\"a\"` for the link-styled\n * case.\n */\nconst ROLE_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n checkbox: { primitive: 'Checkbox', path: '@adea-ai/ui/components/ui/checkbox' },\n radio: { primitive: 'RadioGroup', path: '@adea-ai/ui/components/ui/radio-group' },\n switch: { primitive: 'Switch', path: '@adea-ai/ui/components/ui/switch' },\n tab: { primitive: 'Tabs', path: '@adea-ai/ui/components/ui/tabs' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n menuitem: { primitive: 'DropdownMenu', path: '@adea-ai/ui/components/ui/dropdown-menu' },\n textbox: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n })\n\n/**\n * The elements whose only job is layout or text. A click handler or a tabindex on\n * one of these is a control being hand-built out of a non-control.\n */\nconst GENERIC_ELEMENTS: ReadonlySet<string> = new Set([\n 'div',\n 'span',\n 'li',\n 'ul',\n 'ol',\n 'td',\n 'th',\n 'p',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'section',\n 'article',\n 'aside',\n 'header',\n 'footer',\n 'nav',\n 'main',\n 'img',\n 'svg',\n])\n\nconst CLICK_HANDLERS: ReadonlySet<string> = new Set([\n 'onClick',\n 'onDoubleClick',\n 'onPointerDown',\n 'onPointerUp',\n 'onMouseDown',\n 'onMouseUp',\n 'on:click',\n 'on:dblclick',\n 'on:pointerdown',\n 'on:pointerup',\n 'on:mousedown',\n 'on:mouseup',\n])\n\ninterface InteractiveWrapperOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\n/** Only statically knowable branches; unresolved roles remain a review boundary. */\nconst staticStrings = (node: any): string[] => {\n if (node?.type === 'Literal' && typeof node.value === 'string') return [node.value]\n if (node?.type === 'JSXExpressionContainer') return staticStrings(node.expression)\n if (node?.type === 'ConditionalExpression')\n return [...staticStrings(node.consequent), ...staticStrings(node.alternate)]\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return [node.quasis[0].value.cooked ?? node.quasis[0].value.raw]\n return []\n}\n\nconst roleOf = (node: any): string | null => {\n for (const attribute of node.attributes ?? []) {\n if (attribute.type !== 'JSXAttribute' || attribute.name?.name !== 'role') continue\n return (\n staticStrings(attribute.value)\n .flatMap((role) => role.split(/\\s+/))\n .find((role) => role in ROLE_PRIMITIVES) ?? null\n )\n }\n return null\n}\n\n/** Whether the element carries the attribute, by name. */\nconst has = (node: any, name: string): boolean =>\n (node.attributes ?? []).some(\n (attribute: any) =>\n attribute.type === 'JSXAttribute' &&\n (attribute.name?.type === 'JSXNamespacedName'\n ? `${attribute.name.namespace.name}:${attribute.name.name.name}`\n : attribute.name?.name) === name\n )\n\nconst hasTabStop = (node: any): boolean =>\n (node.attributes ?? []).some((attribute: any) => {\n if (\n attribute.type !== 'JSXAttribute' ||\n !['tabIndex', 'tabindex'].includes(attribute.name?.name)\n )\n return false\n const value =\n attribute.value?.type === 'JSXExpressionContainer'\n ? attribute.value.expression\n : attribute.value\n if (value?.type === 'Literal') return !(Number(value.value) < 0)\n if (\n value?.type === 'UnaryExpression' &&\n value.operator === '-' &&\n value.argument?.type === 'Literal'\n )\n return !(Number(value.argument.value) > 0)\n return true\n })\n\nconst noInteractiveWrappers: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Generic elements dressed as interactive controls — an interactive role, a click handler or a tabindex on a layout element — re-implement a primitive by hand. Compose the real component.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n roleWrapper:\n 'A {{element}} with role=\"{{role}}\" hand-builds {{primitive}}: use {{primitive}} from {{path}}. The primitive brings keyboard activation, focus behaviour and ARIA wiring; the wrapper re-implements them badly.',\n clickWrapper:\n 'A click handler on {{element}} composes {{primitive}} by hand: use {{primitive}} from {{path}}. A div that reacts to clicks is a button that cannot be reached by keyboard.',\n tabindexWrapper:\n 'A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as InteractiveWrapperOptions\n const allowed = new Set(options.allow ?? [])\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return\n // A capitalized element is a component — often a design-system primitive\n // composing a role deliberately (a Button carrying role=\"option\" inside a\n // custom listbox, for example). That is composition, not impersonation.\n if (element[0] === element[0]?.toUpperCase()) return\n\n const role = roleOf(node)\n if (role && role in ROLE_PRIMITIVES) {\n const target = ROLE_PRIMITIVES[role]!\n context.report({\n node,\n messageId: 'roleWrapper',\n data: { element, role, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n\n if (GENERIC_ELEMENTS.has(element)) {\n const click = [...CLICK_HANDLERS].find((handler) => has(node, handler))\n if (click) {\n const target = ROLE_PRIMITIVES.button!\n context.report({\n node,\n messageId: 'clickWrapper',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n if (hasTabStop(node)) {\n context.report({\n node,\n messageId: 'tabindexWrapper',\n data: { element },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n }\n },\n }\n },\n}\n\nconst noPrimitiveLibraryImports: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'The primitive libraries the design system is built on (Kobalte and friends) are its internal affair — compose the exported components instead of importing the primitives.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n packages: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n primitiveImport:\n 'Import from the design system, not from {{package}}: the primitives it wraps carry the focus, keyboard and ARIA behaviour the components exist to provide.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as PrimitiveImportOptions\n const prefixes = options.packages ?? DEFAULT_PRIMITIVE_PACKAGES\n const offender = (source: unknown): string | null => {\n const name = String(source ?? '')\n return prefixes.find((prefix) => name === prefix || name.startsWith(`${prefix}/`)) ?? null\n }\n const report = (node: unknown, source: unknown) => {\n const pkg = offender(source)!\n context.report({\n node,\n messageId: 'primitiveImport',\n data: { package: pkg },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n ImportDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ImportExpression(node: any) {\n if (typeof node.source?.value === 'string' && offender(node.source.value))\n report(node, node.source.value)\n },\n ExportNamedDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ExportAllDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n CallExpression(node: any) {\n if (node.callee?.type !== 'Identifier' || node.callee.name !== 'require') return\n const source = node.arguments?.[0]?.value\n if (typeof source === 'string' && offender(source)) report(node, source)\n },\n }\n },\n}\n\nconst plugin = {\n meta: { name: 'adea' },\n rules: {\n 'no-inline-styles': noInlineStyles,\n 'no-class-list': noClassList,\n 'no-raw-interactive-elements': noRawInteractiveElements,\n 'no-primitive-library-imports': noPrimitiveLibraryImports,\n 'no-interactive-wrappers': noInteractiveWrappers,\n },\n}\n\nexport default plugin\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lint.js","names":[],"sources":["../src/lint/index.ts"],"sourcesContent":["/*\n * The design system's own oxlint plugin.\n *\n * The styling rules live in `@shadcn/lint`; what that plugin cannot know is which\n * *elements* the design system owns. This is the other half of the contract, for\n * the applications that consume the library: an interactive element this package\n * provides is not re-implemented at the application layer, and the primitive\n * libraries this package is built from are not imported around it.\n *\n * Loaded as an oxlint jsPlugin:\n *\n * // .oxlintrc.json\n * { \"jsPlugins\": [\"@adea-ai/ui/lint\"], \"rules\": { \"adea/no-raw-interactive-elements\": \"error\" } }\n *\n * Rules are errors by intent. A violation means an application composed its own\n * control out of raw markup — unreachable focus behaviour, an absent keyboard\n * story, a second implementation of a decision this package exists to make once.\n */\n\ntype Json = string | number | boolean | null | Json[] | { [key: string]: Json }\n\n/** The raw elements this package replaces, and the primitive that replaces them. */\nconst ELEMENT_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n input: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n textarea: { primitive: 'Textarea', path: '@adea-ai/ui/components/ui/textarea' },\n select: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n label: { primitive: 'Label', path: '@adea-ai/ui/components/ui/label' },\n })\n\ntype RuleModule = {\n meta: {\n type: 'problem'\n docs: { description: string }\n schema: Json[]\n messages: Record<string, string>\n }\n create: (context: LintContext) => Record<string, (node: any) => void>\n}\n\ninterface LintContext {\n options?: unknown[]\n report: (descriptor: {\n node: unknown\n messageId?: string\n message?: string\n data?: Record<string, Json>\n }) => void\n}\n\n/** Reads a static object-property key without evaluating a computed expression. */\nconst staticPropertyKey = (node: any, computed: boolean): string | null => {\n if (!computed && node?.type === 'Identifier') return node.name\n if (node?.type === 'Literal' && typeof node.value === 'string') return node.value\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw\n return null\n}\n\n/** Detects named props in a literal JSX spread, including nested literal spreads. */\nconst objectHasStaticProperty = (node: any, name: string, seen = new Set<unknown>()): boolean => {\n if (!node || seen.has(node)) return false\n seen.add(node)\n if (\n node.type === 'TSAsExpression' ||\n node.type === 'TSSatisfiesExpression' ||\n node.type === 'TSNonNullExpression'\n )\n return objectHasStaticProperty(node.expression, name, seen)\n if (node.type !== 'ObjectExpression') return false\n return node.properties.some((property: any) => {\n if (property.type === 'Property')\n return staticPropertyKey(property.key, property.computed === true) === name\n if (property.type === 'SpreadElement')\n return objectHasStaticProperty(property.argument, name, seen)\n return false\n })\n}\n\n/** Returns literal values for a statically named property in an object spread. */\nconst staticObjectPropertyValues = (node: any, name: string, seen = new Set<unknown>()): any[] => {\n if (!node || seen.has(node)) return []\n seen.add(node)\n if (\n node.type === 'TSAsExpression' ||\n node.type === 'TSSatisfiesExpression' ||\n node.type === 'TSNonNullExpression'\n )\n return staticObjectPropertyValues(node.expression, name, seen)\n if (node.type !== 'ObjectExpression') return []\n return node.properties.flatMap((property: any) => {\n if (property.type === 'Property')\n return staticPropertyKey(property.key, property.computed === true) === name\n ? [property.value]\n : []\n if (property.type === 'SpreadElement')\n return staticObjectPropertyValues(property.argument, name, seen)\n return []\n })\n}\n\nconst noInlineStyles: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Consumer components cannot set appearance or layout with JSX style props; use shared variants and token-backed classes.',\n },\n schema: [],\n messages: {\n inlineStyle:\n 'JSX style attributes are not permitted, including layout values and CSS custom properties. Use shared variants and token-backed classes.',\n inlineStyleElement:\n 'A JSX style element bypasses the shared design system. Use shared variants and token-backed classes.',\n inlineStyleSpread:\n 'A JSX spread contains a style prop. Pass named shared component props instead of hiding inline styles in a spread.',\n },\n },\n create(context) {\n return {\n JSXAttribute(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'style')\n context.report({ node, messageId: 'inlineStyle' })\n },\n JSXOpeningElement(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'style')\n context.report({ node, messageId: 'inlineStyleElement' })\n },\n JSXSpreadAttribute(node: any) {\n if (objectHasStaticProperty(node.argument, 'style'))\n context.report({ node, messageId: 'inlineStyleSpread' })\n },\n }\n },\n}\n\nconst noClassList: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Solid classList props hide conditional classes from static design-system lint; use cn() with its object-key form.',\n },\n schema: [],\n messages: {\n classList:\n 'classList hides classes from design-system lint. Use cn() with its object-key form and statically readable class names.',\n classListSpread:\n 'A JSX spread contains classList. Use cn() with its object-key form instead of hiding classes in a spread.',\n },\n },\n create(context) {\n return {\n JSXAttribute(node: any) {\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'classList')\n context.report({ node, messageId: 'classList' })\n },\n JSXSpreadAttribute(node: any) {\n if (objectHasStaticProperty(node.argument, 'classList'))\n context.report({ node, messageId: 'classListSpread' })\n },\n }\n },\n}\n\ninterface RawElementOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst noRawInteractiveElements: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n rawElement:\n '{{element}} is provided by the design system: use {{primitive}} from {{path}}. A raw element has no keyboard story, no focus behaviour and no token styling — that is the work the primitive already did.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as RawElementOptions\n const allowed = new Set(options.allow ?? [])\n const emit = (node: unknown, element: string) => {\n const target = ELEMENT_PRIMITIVES[element]!\n context.report({\n node,\n messageId: 'rawElement',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return\n emit(node, element)\n },\n }\n },\n}\n\ninterface PrimitiveImportOptions {\n /** Package prefixes this rule reports. Defaults to the primitive libraries the ui package wraps. */\n packages?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([\n '@kobalte/core',\n '@corvu',\n 'cmdk-solid',\n '@ark-ui',\n '@zag-js',\n '@radix-ui',\n 'radix-ui',\n '@base-ui-components',\n '@base-ui',\n])\n\n/**\n * The interactive ARIA roles an application can dress a generic element in, and\n * the primitive that owns each role. `link` is deliberately absent: an `<a>` is\n * natively interactive and Button itself supports `as=\"a\"` for the link-styled\n * case.\n */\nconst ROLE_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n checkbox: { primitive: 'Checkbox', path: '@adea-ai/ui/components/ui/checkbox' },\n radio: { primitive: 'RadioGroup', path: '@adea-ai/ui/components/ui/radio-group' },\n switch: { primitive: 'Switch', path: '@adea-ai/ui/components/ui/switch' },\n tab: { primitive: 'Tabs', path: '@adea-ai/ui/components/ui/tabs' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n menuitem: { primitive: 'DropdownMenu', path: '@adea-ai/ui/components/ui/dropdown-menu' },\n textbox: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n })\n\n/**\n * The elements whose only job is layout or text. A click handler or a tabindex on\n * one of these is a control being hand-built out of a non-control.\n */\nconst GENERIC_ELEMENTS: ReadonlySet<string> = new Set([\n 'div',\n 'span',\n 'li',\n 'ul',\n 'ol',\n 'td',\n 'th',\n 'p',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'section',\n 'article',\n 'aside',\n 'header',\n 'footer',\n 'nav',\n 'main',\n 'img',\n 'svg',\n])\n\nconst CLICK_HANDLERS: ReadonlySet<string> = new Set([\n 'onClick',\n 'onDoubleClick',\n 'onPointerDown',\n 'onPointerUp',\n 'onMouseDown',\n 'onMouseUp',\n 'on:click',\n 'on:dblclick',\n 'on:pointerdown',\n 'on:pointerup',\n 'on:mousedown',\n 'on:mouseup',\n])\n\ninterface InteractiveWrapperOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\n/** Only statically knowable branches; unresolved roles remain a review boundary. */\nconst staticStrings = (node: any): string[] => {\n if (node?.type === 'Literal' && typeof node.value === 'string') return [node.value]\n if (node?.type === 'JSXExpressionContainer') return staticStrings(node.expression)\n if (node?.type === 'ConditionalExpression')\n return [...staticStrings(node.consequent), ...staticStrings(node.alternate)]\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return [node.quasis[0].value.cooked ?? node.quasis[0].value.raw]\n return []\n}\n\nconst roleOf = (node: any): string | null => {\n for (const attribute of node.attributes ?? []) {\n if (attribute.type === 'JSXSpreadAttribute') {\n const role = staticObjectPropertyValues(attribute.argument, 'role')\n .flatMap((value) => staticStrings(value))\n .flatMap((value) => value.split(/\\s+/))\n .find((value) => value in ROLE_PRIMITIVES)\n if (role) return role\n continue\n }\n if (attribute.type !== 'JSXAttribute' || attribute.name?.name !== 'role') continue\n return (\n staticStrings(attribute.value)\n .flatMap((role) => role.split(/\\s+/))\n .find((role) => role in ROLE_PRIMITIVES) ?? null\n )\n }\n return null\n}\n\n/** Whether the element carries the attribute, by name. */\nconst has = (node: any, name: string): boolean =>\n (node.attributes ?? []).some(\n (attribute: any) =>\n (attribute.type === 'JSXAttribute' &&\n (attribute.name?.type === 'JSXNamespacedName'\n ? `${attribute.name.namespace.name}:${attribute.name.name.name}`\n : attribute.name?.name) === name) ||\n (attribute.type === 'JSXSpreadAttribute' && objectHasStaticProperty(attribute.argument, name))\n )\n\nconst tabStopValue = (value: any): boolean => {\n const expression = value?.type === 'JSXExpressionContainer' ? value.expression : value\n if (expression?.type === 'Literal') return !(Number(expression.value) < 0)\n if (\n expression?.type === 'UnaryExpression' &&\n expression.operator === '-' &&\n expression.argument?.type === 'Literal'\n )\n return !(Number(expression.argument.value) > 0)\n return true\n}\n\nconst hasTabStop = (node: any): boolean =>\n (node.attributes ?? []).some((attribute: any) => {\n if (\n attribute.type === 'JSXAttribute' &&\n ['tabIndex', 'tabindex'].includes(attribute.name?.name)\n )\n return tabStopValue(attribute.value)\n if (attribute.type === 'JSXSpreadAttribute')\n return ['tabIndex', 'tabindex'].some((name) =>\n staticObjectPropertyValues(attribute.argument, name).some(tabStopValue)\n )\n return false\n })\n\nconst noInteractiveWrappers: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Generic elements dressed as interactive controls — an interactive role, a click handler or a tabindex on a layout element — re-implement a primitive by hand. Compose the real component.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n roleWrapper:\n 'A {{element}} with role=\"{{role}}\" hand-builds {{primitive}}: use {{primitive}} from {{path}}. The primitive brings keyboard activation, focus behaviour and ARIA wiring; the wrapper re-implements them badly.',\n clickWrapper:\n 'A click handler on {{element}} composes {{primitive}} by hand: use {{primitive}} from {{path}}. A div that reacts to clicks is a button that cannot be reached by keyboard.',\n tabindexWrapper:\n 'A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as InteractiveWrapperOptions\n const allowed = new Set(options.allow ?? [])\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return\n // A capitalized element is a component — often a design-system primitive\n // composing a role deliberately (a Button carrying role=\"option\" inside a\n // custom listbox, for example). That is composition, not impersonation.\n if (element[0] === element[0]?.toUpperCase()) return\n\n const role = roleOf(node)\n if (role && role in ROLE_PRIMITIVES) {\n const target = ROLE_PRIMITIVES[role]!\n context.report({\n node,\n messageId: 'roleWrapper',\n data: { element, role, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n\n if (GENERIC_ELEMENTS.has(element)) {\n const click = [...CLICK_HANDLERS].find((handler) => has(node, handler))\n if (click) {\n const target = ROLE_PRIMITIVES.button!\n context.report({\n node,\n messageId: 'clickWrapper',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n if (hasTabStop(node)) {\n context.report({\n node,\n messageId: 'tabindexWrapper',\n data: { element },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n }\n },\n }\n },\n}\n\nconst BUTTON_MODULE = '@adea-ai/ui/components/ui/button'\nconst ACTION_BUTTON_MODULE = '@adea-ai/ui/components/composites/action-button'\nconst ICON_BUTTON_SIZES: ReadonlySet<string> = new Set([\n 'icon-2xs',\n 'icon-xs',\n 'icon-sm',\n 'icon-md',\n 'icon-lg',\n 'icon-xl',\n])\n\nconst staticJsxValues = (value: any): any[] =>\n value?.type === 'JSXExpressionContainer' ? [value.expression] : value ? [value] : []\n\nconst attributeValues = (node: any, name: string): any[] => {\n const values: any[] = []\n for (const attribute of node.attributes ?? []) {\n if (attribute.type === 'JSXAttribute' && attribute.name?.name === name)\n values.splice(0, values.length, ...staticJsxValues(attribute.value))\n if (attribute.type === 'JSXSpreadAttribute') {\n const spreadValues = staticObjectPropertyValues(attribute.argument, name)\n if (spreadValues.length > 0) values.splice(0, values.length, spreadValues.at(-1))\n }\n }\n return values\n}\n\nconst staticStringValue = (node: any): string | null => {\n if (node?.type === 'Literal' && typeof node.value === 'string') return node.value\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw\n return null\n}\n\nconst isIconButtonSize = (node: any): boolean =>\n attributeValues(node, 'size').some((value) => {\n const size = staticStringValue(value)\n return size !== null && ICON_BUTTON_SIZES.has(size)\n })\n\nconst actionTooltipStatus = (node: any): 'valid' | 'blank' | 'unknown' | 'missing' => {\n const values = attributeValues(node, 'tooltip')\n if (values.length === 0) return 'missing'\n const strings = values.map(staticStringValue)\n if (strings.some((value) => value !== null && value.trim().length > 0)) return 'valid'\n if (strings.every((value) => value !== null)) return 'blank'\n return 'unknown'\n}\n\nconst isImportedComponent = (identifier: any, imports: Set<string>): boolean =>\n identifier?.type === 'Identifier' && imports.has(identifier.name)\n\n/** Requires shared icon actions to carry the shared explanatory-tooltip contract. */\nconst requireActionButtonTooltip: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Consumer icon-size shared Button actions use ActionButton with a nonblank tooltip; dynamic values remain a review boundary.',\n },\n schema: [],\n messages: {\n useActionButton:\n 'Icon-size Button actions use ActionButton with a nonblank tooltip. Keep the control name in aria-label; the tooltip explains the action.',\n missingTooltip:\n 'An icon-size ActionButton needs a supplied nonblank tooltip. Keep its accessible name in aria-label as well.',\n },\n },\n create(context) {\n const buttons = new Set<string>()\n const actionButtons = new Set<string>()\n const recordImports = (node: any) => {\n const source = node.source?.value\n const target =\n source === BUTTON_MODULE ? buttons : source === ACTION_BUTTON_MODULE ? actionButtons : null\n if (!target) return\n for (const specifier of node.specifiers ?? []) {\n if (specifier.type !== 'ImportSpecifier') continue\n const imported = specifier.imported?.name ?? specifier.imported?.value\n if (\n (source === BUTTON_MODULE && imported === 'Button') ||\n (source === ACTION_BUTTON_MODULE && imported === 'ActionButton')\n )\n target.add(specifier.local?.name)\n }\n }\n\n return {\n ImportDeclaration: recordImports,\n JSXOpeningElement(node: any) {\n if (!isIconButtonSize(node)) return\n const name = node.name?.type === 'JSXIdentifier' ? node.name.name : null\n const isButton = name !== null && buttons.has(name)\n const isActionButton = name !== null && actionButtons.has(name)\n const asValues = attributeValues(node, 'as')\n const targetsButton = asValues.some((value) => isImportedComponent(value, buttons))\n const targetsActionButton = asValues.some((value) =>\n isImportedComponent(value, actionButtons)\n )\n if (isButton || targetsButton) {\n context.report({ node, messageId: 'useActionButton' })\n return\n }\n if (!isActionButton && !targetsActionButton) return\n const status = actionTooltipStatus(node)\n if (status === 'missing' || status === 'blank')\n context.report({ node, messageId: 'missingTooltip' })\n },\n }\n },\n}\n\nconst noPrimitiveLibraryImports: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'The primitive libraries the design system is built on (Kobalte and friends) are its internal affair — compose the exported components instead of importing the primitives.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n packages: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n primitiveImport:\n 'Import from the design system, not from {{package}}: the primitives it wraps carry the focus, keyboard and ARIA behaviour the components exist to provide.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as PrimitiveImportOptions\n const prefixes = options.packages ?? DEFAULT_PRIMITIVE_PACKAGES\n const offender = (source: unknown): string | null => {\n const name = String(source ?? '')\n return prefixes.find((prefix) => name === prefix || name.startsWith(`${prefix}/`)) ?? null\n }\n const report = (node: unknown, source: unknown) => {\n const pkg = offender(source)!\n context.report({\n node,\n messageId: 'primitiveImport',\n data: { package: pkg },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n ImportDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ImportExpression(node: any) {\n if (typeof node.source?.value === 'string' && offender(node.source.value))\n report(node, node.source.value)\n },\n ExportNamedDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ExportAllDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n CallExpression(node: any) {\n if (node.callee?.type !== 'Identifier' || node.callee.name !== 'require') return\n const source = node.arguments?.[0]?.value\n if (typeof source === 'string' && offender(source)) report(node, source)\n },\n }\n },\n}\n\nconst plugin = {\n meta: { name: 'adea' },\n rules: {\n 'no-inline-styles': noInlineStyles,\n 'no-class-list': noClassList,\n 'no-raw-interactive-elements': noRawInteractiveElements,\n 'no-primitive-library-imports': noPrimitiveLibraryImports,\n 'no-interactive-wrappers': noInteractiveWrappers,\n 'require-action-button-tooltip': requireActionButtonTooltip,\n },\n}\n\nexport default plugin\n"],"mappings":"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"}
|
package/dist/r/registry.json
CHANGED
|
@@ -1558,6 +1558,7 @@
|
|
|
1558
1558
|
"title": "Sidebar Nav",
|
|
1559
1559
|
"description": "",
|
|
1560
1560
|
"dependencies": [
|
|
1561
|
+
"@corvu/resizable@^0.2.5",
|
|
1561
1562
|
"@kobalte/core@^0.13.14",
|
|
1562
1563
|
"lucide-solid@^1.48.0"
|
|
1563
1564
|
],
|
|
@@ -1570,6 +1571,11 @@
|
|
|
1570
1571
|
"type": "registry:ui",
|
|
1571
1572
|
"target": "components/layout/sidebar-nav/index.ts"
|
|
1572
1573
|
},
|
|
1574
|
+
{
|
|
1575
|
+
"path": "src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx",
|
|
1576
|
+
"type": "registry:ui",
|
|
1577
|
+
"target": "components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx"
|
|
1578
|
+
},
|
|
1573
1579
|
{
|
|
1574
1580
|
"path": "src/components/layout/sidebar-nav/sidebar-nav.tsx",
|
|
1575
1581
|
"type": "registry:ui",
|
package/dist/r/sidebar-nav.json
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"title": "Sidebar Nav",
|
|
5
5
|
"description": "",
|
|
6
6
|
"dependencies": [
|
|
7
|
+
"@corvu/resizable@^0.2.5",
|
|
7
8
|
"@kobalte/core@^0.13.14",
|
|
8
9
|
"lucide-solid@^1.48.0"
|
|
9
10
|
],
|
|
@@ -16,6 +17,11 @@
|
|
|
16
17
|
"type": "registry:ui",
|
|
17
18
|
"target": "components/layout/sidebar-nav/index.ts"
|
|
18
19
|
},
|
|
20
|
+
{
|
|
21
|
+
"path": "src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx",
|
|
22
|
+
"type": "registry:ui",
|
|
23
|
+
"target": "components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx"
|
|
24
|
+
},
|
|
19
25
|
{
|
|
20
26
|
"path": "src/components/layout/sidebar-nav/sidebar-nav.tsx",
|
|
21
27
|
"type": "registry:ui",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import Resizable from '@corvu/resizable'
|
|
2
|
+
import { cn } from '../../../lib/utils'
|
|
3
|
+
|
|
4
|
+
export type SidebarNavResizeHandleProps = {
|
|
5
|
+
value: number
|
|
6
|
+
minimum: number
|
|
7
|
+
maximum: number
|
|
8
|
+
step?: number
|
|
9
|
+
label?: string
|
|
10
|
+
controls?: string
|
|
11
|
+
class?: string
|
|
12
|
+
onChange(value: number): void
|
|
13
|
+
onCommit?(value: number): void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resize a contextual sidebar without making its host recreate a separator
|
|
18
|
+
* widget. Corvu owns pointer capture, cancellation, constraints, keyboard
|
|
19
|
+
* movement and cleanup. Its two invisible panels provide a bounded pixel
|
|
20
|
+
* ruler; the host retains the real pane layout and preference persistence.
|
|
21
|
+
* Place this inside the positioned sidebar. The ruler can extend over the
|
|
22
|
+
* adjacent content, but only its handle participates in pointer hit testing.
|
|
23
|
+
*/
|
|
24
|
+
export function SidebarNavResizeHandle(props: SidebarNavResizeHandleProps) {
|
|
25
|
+
const maximum = () => Math.max(1, props.maximum)
|
|
26
|
+
const minimum = () => Math.min(maximum(), Math.max(0, props.minimum))
|
|
27
|
+
const value = () => Math.min(maximum(), Math.max(minimum(), props.value))
|
|
28
|
+
const commit = () => props.onCommit?.(value())
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Resizable
|
|
32
|
+
orientation="horizontal"
|
|
33
|
+
sizes={[value() / maximum(), 1 - value() / maximum()]}
|
|
34
|
+
keyboardDelta={`${props.step ?? 16}px`}
|
|
35
|
+
style={{ width: `${maximum()}px` }}
|
|
36
|
+
class={cn('pointer-events-none absolute inset-y-0 start-0 flex', props.class)}
|
|
37
|
+
onSizesChange={(sizes) => {
|
|
38
|
+
const next = Math.round((sizes[0] ?? 0) * maximum())
|
|
39
|
+
if (next !== value()) props.onChange(Math.min(maximum(), Math.max(minimum(), next)))
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<Resizable.Panel minSize={minimum() / maximum()} maxSize={1} aria-hidden="true" />
|
|
43
|
+
<Resizable.Handle
|
|
44
|
+
aria-label={props.label ?? 'Resize navigation'}
|
|
45
|
+
aria-controls={props.controls}
|
|
46
|
+
aria-orientation="vertical"
|
|
47
|
+
aria-valuemin={minimum()}
|
|
48
|
+
aria-valuemax={maximum()}
|
|
49
|
+
aria-valuenow={value()}
|
|
50
|
+
onHandleDragEnd={commit}
|
|
51
|
+
onKeyUp={(event) => {
|
|
52
|
+
if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) commit()
|
|
53
|
+
}}
|
|
54
|
+
class="pointer-events-auto relative z-(--z-docked) w-px -translate-x-full shrink-0 bg-transparent after:absolute after:inset-y-0 after:-inset-x-1 after:w-3 hover:bg-primary focus-visible:bg-primary focus-visible:ring-3 focus-visible:ring-primary-subtle focus-visible:outline-none"
|
|
55
|
+
/>
|
|
56
|
+
<Resizable.Panel minSize={0} aria-hidden="true" />
|
|
57
|
+
</Resizable>
|
|
58
|
+
)
|
|
59
|
+
}
|
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -1558,6 +1558,7 @@
|
|
|
1558
1558
|
"title": "Sidebar Nav",
|
|
1559
1559
|
"description": "",
|
|
1560
1560
|
"dependencies": [
|
|
1561
|
+
"@corvu/resizable@^0.2.5",
|
|
1561
1562
|
"@kobalte/core@^0.13.14",
|
|
1562
1563
|
"lucide-solid@^1.48.0"
|
|
1563
1564
|
],
|
|
@@ -1570,6 +1571,11 @@
|
|
|
1570
1571
|
"type": "registry:ui",
|
|
1571
1572
|
"target": "components/layout/sidebar-nav/index.ts"
|
|
1572
1573
|
},
|
|
1574
|
+
{
|
|
1575
|
+
"path": "src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx",
|
|
1576
|
+
"type": "registry:ui",
|
|
1577
|
+
"target": "components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx"
|
|
1578
|
+
},
|
|
1573
1579
|
{
|
|
1574
1580
|
"path": "src/components/layout/sidebar-nav/sidebar-nav.tsx",
|
|
1575
1581
|
"type": "registry:ui",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import Resizable from '@corvu/resizable'
|
|
2
|
+
import { cn } from '#lib/utils'
|
|
3
|
+
|
|
4
|
+
export type SidebarNavResizeHandleProps = {
|
|
5
|
+
value: number
|
|
6
|
+
minimum: number
|
|
7
|
+
maximum: number
|
|
8
|
+
step?: number
|
|
9
|
+
label?: string
|
|
10
|
+
controls?: string
|
|
11
|
+
class?: string
|
|
12
|
+
onChange(value: number): void
|
|
13
|
+
onCommit?(value: number): void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resize a contextual sidebar without making its host recreate a separator
|
|
18
|
+
* widget. Corvu owns pointer capture, cancellation, constraints, keyboard
|
|
19
|
+
* movement and cleanup. Its two invisible panels provide a bounded pixel
|
|
20
|
+
* ruler; the host retains the real pane layout and preference persistence.
|
|
21
|
+
* Place this inside the positioned sidebar. The ruler can extend over the
|
|
22
|
+
* adjacent content, but only its handle participates in pointer hit testing.
|
|
23
|
+
*/
|
|
24
|
+
export function SidebarNavResizeHandle(props: SidebarNavResizeHandleProps) {
|
|
25
|
+
const maximum = () => Math.max(1, props.maximum)
|
|
26
|
+
const minimum = () => Math.min(maximum(), Math.max(0, props.minimum))
|
|
27
|
+
const value = () => Math.min(maximum(), Math.max(minimum(), props.value))
|
|
28
|
+
const commit = () => props.onCommit?.(value())
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Resizable
|
|
32
|
+
orientation="horizontal"
|
|
33
|
+
sizes={[value() / maximum(), 1 - value() / maximum()]}
|
|
34
|
+
keyboardDelta={`${props.step ?? 16}px`}
|
|
35
|
+
style={{ width: `${maximum()}px` }}
|
|
36
|
+
class={cn('pointer-events-none absolute inset-y-0 start-0 flex', props.class)}
|
|
37
|
+
onSizesChange={(sizes) => {
|
|
38
|
+
const next = Math.round((sizes[0] ?? 0) * maximum())
|
|
39
|
+
if (next !== value()) props.onChange(Math.min(maximum(), Math.max(minimum(), next)))
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<Resizable.Panel minSize={minimum() / maximum()} maxSize={1} aria-hidden="true" />
|
|
43
|
+
<Resizable.Handle
|
|
44
|
+
aria-label={props.label ?? 'Resize navigation'}
|
|
45
|
+
aria-controls={props.controls}
|
|
46
|
+
aria-orientation="vertical"
|
|
47
|
+
aria-valuemin={minimum()}
|
|
48
|
+
aria-valuemax={maximum()}
|
|
49
|
+
aria-valuenow={value()}
|
|
50
|
+
onHandleDragEnd={commit}
|
|
51
|
+
onKeyUp={(event) => {
|
|
52
|
+
if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) commit()
|
|
53
|
+
}}
|
|
54
|
+
class="pointer-events-auto relative z-(--z-docked) w-px -translate-x-full shrink-0 bg-transparent after:absolute after:inset-y-0 after:-inset-x-1 after:w-3 hover:bg-primary focus-visible:bg-primary focus-visible:ring-3 focus-visible:ring-primary-subtle focus-visible:outline-none"
|
|
55
|
+
/>
|
|
56
|
+
<Resizable.Panel minSize={0} aria-hidden="true" />
|
|
57
|
+
</Resizable>
|
|
58
|
+
)
|
|
59
|
+
}
|
package/src/lint/index.ts
CHANGED
|
@@ -79,6 +79,28 @@ const objectHasStaticProperty = (node: any, name: string, seen = new Set<unknown
|
|
|
79
79
|
})
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** Returns literal values for a statically named property in an object spread. */
|
|
83
|
+
const staticObjectPropertyValues = (node: any, name: string, seen = new Set<unknown>()): any[] => {
|
|
84
|
+
if (!node || seen.has(node)) return []
|
|
85
|
+
seen.add(node)
|
|
86
|
+
if (
|
|
87
|
+
node.type === 'TSAsExpression' ||
|
|
88
|
+
node.type === 'TSSatisfiesExpression' ||
|
|
89
|
+
node.type === 'TSNonNullExpression'
|
|
90
|
+
)
|
|
91
|
+
return staticObjectPropertyValues(node.expression, name, seen)
|
|
92
|
+
if (node.type !== 'ObjectExpression') return []
|
|
93
|
+
return node.properties.flatMap((property: any) => {
|
|
94
|
+
if (property.type === 'Property')
|
|
95
|
+
return staticPropertyKey(property.key, property.computed === true) === name
|
|
96
|
+
? [property.value]
|
|
97
|
+
: []
|
|
98
|
+
if (property.type === 'SpreadElement')
|
|
99
|
+
return staticObjectPropertyValues(property.argument, name, seen)
|
|
100
|
+
return []
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
82
104
|
const noInlineStyles: RuleModule = {
|
|
83
105
|
meta: {
|
|
84
106
|
type: 'problem',
|
|
@@ -297,6 +319,14 @@ const staticStrings = (node: any): string[] => {
|
|
|
297
319
|
|
|
298
320
|
const roleOf = (node: any): string | null => {
|
|
299
321
|
for (const attribute of node.attributes ?? []) {
|
|
322
|
+
if (attribute.type === 'JSXSpreadAttribute') {
|
|
323
|
+
const role = staticObjectPropertyValues(attribute.argument, 'role')
|
|
324
|
+
.flatMap((value) => staticStrings(value))
|
|
325
|
+
.flatMap((value) => value.split(/\s+/))
|
|
326
|
+
.find((value) => value in ROLE_PRIMITIVES)
|
|
327
|
+
if (role) return role
|
|
328
|
+
continue
|
|
329
|
+
}
|
|
300
330
|
if (attribute.type !== 'JSXAttribute' || attribute.name?.name !== 'role') continue
|
|
301
331
|
return (
|
|
302
332
|
staticStrings(attribute.value)
|
|
@@ -311,31 +341,37 @@ const roleOf = (node: any): string | null => {
|
|
|
311
341
|
const has = (node: any, name: string): boolean =>
|
|
312
342
|
(node.attributes ?? []).some(
|
|
313
343
|
(attribute: any) =>
|
|
314
|
-
attribute.type === 'JSXAttribute' &&
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
344
|
+
(attribute.type === 'JSXAttribute' &&
|
|
345
|
+
(attribute.name?.type === 'JSXNamespacedName'
|
|
346
|
+
? `${attribute.name.namespace.name}:${attribute.name.name.name}`
|
|
347
|
+
: attribute.name?.name) === name) ||
|
|
348
|
+
(attribute.type === 'JSXSpreadAttribute' && objectHasStaticProperty(attribute.argument, name))
|
|
318
349
|
)
|
|
319
350
|
|
|
351
|
+
const tabStopValue = (value: any): boolean => {
|
|
352
|
+
const expression = value?.type === 'JSXExpressionContainer' ? value.expression : value
|
|
353
|
+
if (expression?.type === 'Literal') return !(Number(expression.value) < 0)
|
|
354
|
+
if (
|
|
355
|
+
expression?.type === 'UnaryExpression' &&
|
|
356
|
+
expression.operator === '-' &&
|
|
357
|
+
expression.argument?.type === 'Literal'
|
|
358
|
+
)
|
|
359
|
+
return !(Number(expression.argument.value) > 0)
|
|
360
|
+
return true
|
|
361
|
+
}
|
|
362
|
+
|
|
320
363
|
const hasTabStop = (node: any): boolean =>
|
|
321
364
|
(node.attributes ?? []).some((attribute: any) => {
|
|
322
365
|
if (
|
|
323
|
-
attribute.type
|
|
324
|
-
|
|
325
|
-
)
|
|
326
|
-
return false
|
|
327
|
-
const value =
|
|
328
|
-
attribute.value?.type === 'JSXExpressionContainer'
|
|
329
|
-
? attribute.value.expression
|
|
330
|
-
: attribute.value
|
|
331
|
-
if (value?.type === 'Literal') return !(Number(value.value) < 0)
|
|
332
|
-
if (
|
|
333
|
-
value?.type === 'UnaryExpression' &&
|
|
334
|
-
value.operator === '-' &&
|
|
335
|
-
value.argument?.type === 'Literal'
|
|
366
|
+
attribute.type === 'JSXAttribute' &&
|
|
367
|
+
['tabIndex', 'tabindex'].includes(attribute.name?.name)
|
|
336
368
|
)
|
|
337
|
-
return
|
|
338
|
-
|
|
369
|
+
return tabStopValue(attribute.value)
|
|
370
|
+
if (attribute.type === 'JSXSpreadAttribute')
|
|
371
|
+
return ['tabIndex', 'tabindex'].some((name) =>
|
|
372
|
+
staticObjectPropertyValues(attribute.argument, name).some(tabStopValue)
|
|
373
|
+
)
|
|
374
|
+
return false
|
|
339
375
|
})
|
|
340
376
|
|
|
341
377
|
const noInteractiveWrappers: RuleModule = {
|
|
@@ -415,6 +451,118 @@ const noInteractiveWrappers: RuleModule = {
|
|
|
415
451
|
},
|
|
416
452
|
}
|
|
417
453
|
|
|
454
|
+
const BUTTON_MODULE = '@adea-ai/ui/components/ui/button'
|
|
455
|
+
const ACTION_BUTTON_MODULE = '@adea-ai/ui/components/composites/action-button'
|
|
456
|
+
const ICON_BUTTON_SIZES: ReadonlySet<string> = new Set([
|
|
457
|
+
'icon-2xs',
|
|
458
|
+
'icon-xs',
|
|
459
|
+
'icon-sm',
|
|
460
|
+
'icon-md',
|
|
461
|
+
'icon-lg',
|
|
462
|
+
'icon-xl',
|
|
463
|
+
])
|
|
464
|
+
|
|
465
|
+
const staticJsxValues = (value: any): any[] =>
|
|
466
|
+
value?.type === 'JSXExpressionContainer' ? [value.expression] : value ? [value] : []
|
|
467
|
+
|
|
468
|
+
const attributeValues = (node: any, name: string): any[] => {
|
|
469
|
+
const values: any[] = []
|
|
470
|
+
for (const attribute of node.attributes ?? []) {
|
|
471
|
+
if (attribute.type === 'JSXAttribute' && attribute.name?.name === name)
|
|
472
|
+
values.splice(0, values.length, ...staticJsxValues(attribute.value))
|
|
473
|
+
if (attribute.type === 'JSXSpreadAttribute') {
|
|
474
|
+
const spreadValues = staticObjectPropertyValues(attribute.argument, name)
|
|
475
|
+
if (spreadValues.length > 0) values.splice(0, values.length, spreadValues.at(-1))
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return values
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const staticStringValue = (node: any): string | null => {
|
|
482
|
+
if (node?.type === 'Literal' && typeof node.value === 'string') return node.value
|
|
483
|
+
if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)
|
|
484
|
+
return node.quasis[0].value.cooked ?? node.quasis[0].value.raw
|
|
485
|
+
return null
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const isIconButtonSize = (node: any): boolean =>
|
|
489
|
+
attributeValues(node, 'size').some((value) => {
|
|
490
|
+
const size = staticStringValue(value)
|
|
491
|
+
return size !== null && ICON_BUTTON_SIZES.has(size)
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
const actionTooltipStatus = (node: any): 'valid' | 'blank' | 'unknown' | 'missing' => {
|
|
495
|
+
const values = attributeValues(node, 'tooltip')
|
|
496
|
+
if (values.length === 0) return 'missing'
|
|
497
|
+
const strings = values.map(staticStringValue)
|
|
498
|
+
if (strings.some((value) => value !== null && value.trim().length > 0)) return 'valid'
|
|
499
|
+
if (strings.every((value) => value !== null)) return 'blank'
|
|
500
|
+
return 'unknown'
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const isImportedComponent = (identifier: any, imports: Set<string>): boolean =>
|
|
504
|
+
identifier?.type === 'Identifier' && imports.has(identifier.name)
|
|
505
|
+
|
|
506
|
+
/** Requires shared icon actions to carry the shared explanatory-tooltip contract. */
|
|
507
|
+
const requireActionButtonTooltip: RuleModule = {
|
|
508
|
+
meta: {
|
|
509
|
+
type: 'problem',
|
|
510
|
+
docs: {
|
|
511
|
+
description:
|
|
512
|
+
'Consumer icon-size shared Button actions use ActionButton with a nonblank tooltip; dynamic values remain a review boundary.',
|
|
513
|
+
},
|
|
514
|
+
schema: [],
|
|
515
|
+
messages: {
|
|
516
|
+
useActionButton:
|
|
517
|
+
'Icon-size Button actions use ActionButton with a nonblank tooltip. Keep the control name in aria-label; the tooltip explains the action.',
|
|
518
|
+
missingTooltip:
|
|
519
|
+
'An icon-size ActionButton needs a supplied nonblank tooltip. Keep its accessible name in aria-label as well.',
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
create(context) {
|
|
523
|
+
const buttons = new Set<string>()
|
|
524
|
+
const actionButtons = new Set<string>()
|
|
525
|
+
const recordImports = (node: any) => {
|
|
526
|
+
const source = node.source?.value
|
|
527
|
+
const target =
|
|
528
|
+
source === BUTTON_MODULE ? buttons : source === ACTION_BUTTON_MODULE ? actionButtons : null
|
|
529
|
+
if (!target) return
|
|
530
|
+
for (const specifier of node.specifiers ?? []) {
|
|
531
|
+
if (specifier.type !== 'ImportSpecifier') continue
|
|
532
|
+
const imported = specifier.imported?.name ?? specifier.imported?.value
|
|
533
|
+
if (
|
|
534
|
+
(source === BUTTON_MODULE && imported === 'Button') ||
|
|
535
|
+
(source === ACTION_BUTTON_MODULE && imported === 'ActionButton')
|
|
536
|
+
)
|
|
537
|
+
target.add(specifier.local?.name)
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
ImportDeclaration: recordImports,
|
|
543
|
+
JSXOpeningElement(node: any) {
|
|
544
|
+
if (!isIconButtonSize(node)) return
|
|
545
|
+
const name = node.name?.type === 'JSXIdentifier' ? node.name.name : null
|
|
546
|
+
const isButton = name !== null && buttons.has(name)
|
|
547
|
+
const isActionButton = name !== null && actionButtons.has(name)
|
|
548
|
+
const asValues = attributeValues(node, 'as')
|
|
549
|
+
const targetsButton = asValues.some((value) => isImportedComponent(value, buttons))
|
|
550
|
+
const targetsActionButton = asValues.some((value) =>
|
|
551
|
+
isImportedComponent(value, actionButtons)
|
|
552
|
+
)
|
|
553
|
+
if (isButton || targetsButton) {
|
|
554
|
+
context.report({ node, messageId: 'useActionButton' })
|
|
555
|
+
return
|
|
556
|
+
}
|
|
557
|
+
if (!isActionButton && !targetsActionButton) return
|
|
558
|
+
const status = actionTooltipStatus(node)
|
|
559
|
+
if (status === 'missing' || status === 'blank')
|
|
560
|
+
context.report({ node, messageId: 'missingTooltip' })
|
|
561
|
+
},
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
}
|
|
565
|
+
|
|
418
566
|
const noPrimitiveLibraryImports: RuleModule = {
|
|
419
567
|
meta: {
|
|
420
568
|
type: 'problem',
|
|
@@ -484,6 +632,7 @@ const plugin = {
|
|
|
484
632
|
'no-raw-interactive-elements': noRawInteractiveElements,
|
|
485
633
|
'no-primitive-library-imports': noPrimitiveLibraryImports,
|
|
486
634
|
'no-interactive-wrappers': noInteractiveWrappers,
|
|
635
|
+
'require-action-button-tooltip': requireActionButtonTooltip,
|
|
487
636
|
},
|
|
488
637
|
}
|
|
489
638
|
|