@theruntime/components 0.0.0 → 0.0.1
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/LICENSE +21 -0
- package/package.json +113 -4
- package/src/accordion.tsx +62 -0
- package/src/alert-dialog.tsx +177 -0
- package/src/alert.tsx +60 -0
- package/src/aspect-ratio.tsx +7 -0
- package/src/attachment.tsx +192 -0
- package/src/avatar.tsx +94 -0
- package/src/badge.tsx +46 -0
- package/src/breadcrumb.tsx +102 -0
- package/src/bubble.tsx +125 -0
- package/src/button-group.tsx +82 -0
- package/src/button.tsx +62 -0
- package/src/calendar.tsx +178 -0
- package/src/card.tsx +75 -0
- package/src/carousel.tsx +228 -0
- package/src/chart/container.tsx +72 -0
- package/src/chart/context.tsx +39 -0
- package/src/chart/index.ts +4 -0
- package/src/chart/legend.tsx +63 -0
- package/src/chart/payload.ts +31 -0
- package/src/chart/tooltip.tsx +149 -0
- package/src/checkbox.tsx +27 -0
- package/src/cn.ts +36 -0
- package/src/collapsible.tsx +19 -0
- package/src/combobox/chips.tsx +71 -0
- package/src/combobox/content.tsx +123 -0
- package/src/combobox/index.ts +13 -0
- package/src/combobox/input.tsx +39 -0
- package/src/combobox/trigger.tsx +44 -0
- package/src/command.tsx +153 -0
- package/src/context-menu.tsx +222 -0
- package/src/dialog.tsx +142 -0
- package/src/direction.tsx +18 -0
- package/src/drawer.tsx +122 -0
- package/src/dropdown-menu.tsx +226 -0
- package/src/empty.tsx +94 -0
- package/src/field.tsx +232 -0
- package/src/form.tsx +153 -0
- package/src/hover-card.tsx +36 -0
- package/src/index.ts +90 -0
- package/src/input-group.tsx +156 -0
- package/src/input-otp.tsx +68 -0
- package/src/input.tsx +21 -0
- package/src/item.tsx +172 -0
- package/src/kbd.tsx +28 -0
- package/src/label.tsx +19 -0
- package/src/marker.tsx +66 -0
- package/src/menubar.tsx +250 -0
- package/src/message-scroller.tsx +128 -0
- package/src/message.tsx +85 -0
- package/src/native-select.tsx +56 -0
- package/src/navigation-menu.tsx +161 -0
- package/src/pagination.tsx +106 -0
- package/src/popover.tsx +72 -0
- package/src/progress.tsx +26 -0
- package/src/questionnaire/actions.tsx +118 -0
- package/src/questionnaire/choices.tsx +113 -0
- package/src/questionnaire/index.ts +21 -0
- package/src/questionnaire/root.tsx +75 -0
- package/src/radio-group.tsx +43 -0
- package/src/resizable.tsx +45 -0
- package/src/scroll-area.tsx +54 -0
- package/src/select.tsx +173 -0
- package/src/separator.tsx +26 -0
- package/src/sheet.tsx +132 -0
- package/src/shell/app-tab.tsx +67 -0
- package/src/shell/entry-row.tsx +137 -0
- package/src/shell/field.tsx +83 -0
- package/src/shell/group-head.tsx +19 -0
- package/src/shell/scope-chip.tsx +32 -0
- package/src/shell/suggest-panel.tsx +92 -0
- package/src/sidebar/context.tsx +128 -0
- package/src/sidebar/index.ts +23 -0
- package/src/sidebar/menu.tsx +239 -0
- package/src/sidebar/sections.tsx +118 -0
- package/src/sidebar/sidebar.tsx +183 -0
- package/src/skeleton.tsx +13 -0
- package/src/slider.tsx +56 -0
- package/src/sonner.tsx +41 -0
- package/src/spinner.tsx +16 -0
- package/src/styles.css +149 -0
- package/src/switch.tsx +33 -0
- package/src/table.tsx +90 -0
- package/src/tabs.tsx +79 -0
- package/src/textarea.tsx +18 -0
- package/src/toggle-group.tsx +81 -0
- package/src/toggle.tsx +44 -0
- package/src/tooltip.tsx +51 -0
- package/src/use-mobile.ts +21 -0
- package/README.md +0 -3
package/src/select.tsx
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { CheckIcon, CaretDownIcon, CaretUpIcon } from "@phosphor-icons/react";
|
|
3
|
+
import { Select as SelectPrimitive } from "radix-ui";
|
|
4
|
+
|
|
5
|
+
import { cn } from "./cn";
|
|
6
|
+
|
|
7
|
+
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
|
8
|
+
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
|
12
|
+
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
|
16
|
+
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function SelectTrigger({
|
|
20
|
+
className,
|
|
21
|
+
size = "default",
|
|
22
|
+
children,
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
|
25
|
+
size?: "sm" | "default";
|
|
26
|
+
}) {
|
|
27
|
+
return (
|
|
28
|
+
<SelectPrimitive.Trigger
|
|
29
|
+
data-slot="select-trigger"
|
|
30
|
+
data-size={size}
|
|
31
|
+
className={cn(
|
|
32
|
+
"flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
<SelectPrimitive.Icon asChild>
|
|
39
|
+
<CaretDownIcon className="size-4 opacity-50" />
|
|
40
|
+
</SelectPrimitive.Icon>
|
|
41
|
+
</SelectPrimitive.Trigger>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function SelectContent({
|
|
46
|
+
className,
|
|
47
|
+
children,
|
|
48
|
+
position = "item-aligned",
|
|
49
|
+
align = "center",
|
|
50
|
+
...props
|
|
51
|
+
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
|
52
|
+
return (
|
|
53
|
+
<SelectPrimitive.Portal>
|
|
54
|
+
<SelectPrimitive.Content
|
|
55
|
+
data-slot="select-content"
|
|
56
|
+
className={cn(
|
|
57
|
+
"relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
58
|
+
position === "popper" &&
|
|
59
|
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
60
|
+
className,
|
|
61
|
+
)}
|
|
62
|
+
position={position}
|
|
63
|
+
align={align}
|
|
64
|
+
{...props}
|
|
65
|
+
>
|
|
66
|
+
<SelectScrollUpButton />
|
|
67
|
+
<SelectPrimitive.Viewport
|
|
68
|
+
className={cn(
|
|
69
|
+
"p-1",
|
|
70
|
+
position === "popper" &&
|
|
71
|
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
</SelectPrimitive.Viewport>
|
|
76
|
+
<SelectScrollDownButton />
|
|
77
|
+
</SelectPrimitive.Content>
|
|
78
|
+
</SelectPrimitive.Portal>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
|
83
|
+
return (
|
|
84
|
+
<SelectPrimitive.Label
|
|
85
|
+
data-slot="select-label"
|
|
86
|
+
className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function SelectItem({
|
|
93
|
+
className,
|
|
94
|
+
children,
|
|
95
|
+
...props
|
|
96
|
+
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
97
|
+
return (
|
|
98
|
+
<SelectPrimitive.Item
|
|
99
|
+
data-slot="select-item"
|
|
100
|
+
className={cn(
|
|
101
|
+
"relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
102
|
+
className,
|
|
103
|
+
)}
|
|
104
|
+
{...props}
|
|
105
|
+
>
|
|
106
|
+
<span
|
|
107
|
+
data-slot="select-item-indicator"
|
|
108
|
+
className="absolute right-2 flex size-3.5 items-center justify-center"
|
|
109
|
+
>
|
|
110
|
+
<SelectPrimitive.ItemIndicator>
|
|
111
|
+
<CheckIcon className="size-4" />
|
|
112
|
+
</SelectPrimitive.ItemIndicator>
|
|
113
|
+
</span>
|
|
114
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
115
|
+
</SelectPrimitive.Item>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function SelectSeparator({
|
|
120
|
+
className,
|
|
121
|
+
...props
|
|
122
|
+
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
|
123
|
+
return (
|
|
124
|
+
<SelectPrimitive.Separator
|
|
125
|
+
data-slot="select-separator"
|
|
126
|
+
className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function SelectScrollUpButton({
|
|
133
|
+
className,
|
|
134
|
+
...props
|
|
135
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
|
136
|
+
return (
|
|
137
|
+
<SelectPrimitive.ScrollUpButton
|
|
138
|
+
data-slot="select-scroll-up-button"
|
|
139
|
+
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
140
|
+
{...props}
|
|
141
|
+
>
|
|
142
|
+
<CaretUpIcon className="size-4" />
|
|
143
|
+
</SelectPrimitive.ScrollUpButton>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function SelectScrollDownButton({
|
|
148
|
+
className,
|
|
149
|
+
...props
|
|
150
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
|
151
|
+
return (
|
|
152
|
+
<SelectPrimitive.ScrollDownButton
|
|
153
|
+
data-slot="select-scroll-down-button"
|
|
154
|
+
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
155
|
+
{...props}
|
|
156
|
+
>
|
|
157
|
+
<CaretDownIcon className="size-4" />
|
|
158
|
+
</SelectPrimitive.ScrollDownButton>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export {
|
|
163
|
+
Select,
|
|
164
|
+
SelectContent,
|
|
165
|
+
SelectGroup,
|
|
166
|
+
SelectItem,
|
|
167
|
+
SelectLabel,
|
|
168
|
+
SelectScrollDownButton,
|
|
169
|
+
SelectScrollUpButton,
|
|
170
|
+
SelectSeparator,
|
|
171
|
+
SelectTrigger,
|
|
172
|
+
SelectValue,
|
|
173
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Separator as SeparatorPrimitive } from "radix-ui";
|
|
3
|
+
|
|
4
|
+
import { cn } from "./cn";
|
|
5
|
+
|
|
6
|
+
function Separator({
|
|
7
|
+
className,
|
|
8
|
+
orientation = "horizontal",
|
|
9
|
+
decorative = true,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
|
12
|
+
return (
|
|
13
|
+
<SeparatorPrimitive.Root
|
|
14
|
+
data-slot="separator"
|
|
15
|
+
decorative={decorative}
|
|
16
|
+
orientation={orientation}
|
|
17
|
+
className={cn(
|
|
18
|
+
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
|
19
|
+
className,
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { Separator };
|
package/src/sheet.tsx
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { XIcon } from "@phosphor-icons/react";
|
|
3
|
+
import { Dialog as SheetPrimitive } from "radix-ui";
|
|
4
|
+
|
|
5
|
+
import { cn } from "./cn";
|
|
6
|
+
|
|
7
|
+
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
|
8
|
+
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
|
12
|
+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
|
16
|
+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
|
20
|
+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function SheetOverlay({
|
|
24
|
+
className,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
|
27
|
+
return (
|
|
28
|
+
<SheetPrimitive.Overlay
|
|
29
|
+
data-slot="sheet-overlay"
|
|
30
|
+
className={cn(
|
|
31
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function SheetContent({
|
|
40
|
+
className,
|
|
41
|
+
children,
|
|
42
|
+
side = "right",
|
|
43
|
+
showCloseButton = true,
|
|
44
|
+
...props
|
|
45
|
+
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
|
46
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
47
|
+
showCloseButton?: boolean;
|
|
48
|
+
}) {
|
|
49
|
+
return (
|
|
50
|
+
<SheetPortal>
|
|
51
|
+
<SheetOverlay />
|
|
52
|
+
<SheetPrimitive.Content
|
|
53
|
+
data-slot="sheet-content"
|
|
54
|
+
className={cn(
|
|
55
|
+
"fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
|
|
56
|
+
side === "right" &&
|
|
57
|
+
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
|
58
|
+
side === "left" &&
|
|
59
|
+
"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
|
60
|
+
side === "top" &&
|
|
61
|
+
"inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
62
|
+
side === "bottom" &&
|
|
63
|
+
"inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
{showCloseButton && (
|
|
70
|
+
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary">
|
|
71
|
+
<XIcon className="size-4" />
|
|
72
|
+
<span className="sr-only">Close</span>
|
|
73
|
+
</SheetPrimitive.Close>
|
|
74
|
+
)}
|
|
75
|
+
</SheetPrimitive.Content>
|
|
76
|
+
</SheetPortal>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
81
|
+
return (
|
|
82
|
+
<div
|
|
83
|
+
data-slot="sheet-header"
|
|
84
|
+
className={cn("flex flex-col gap-1.5 p-4", className)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
data-slot="sheet-footer"
|
|
94
|
+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
|
101
|
+
return (
|
|
102
|
+
<SheetPrimitive.Title
|
|
103
|
+
data-slot="sheet-title"
|
|
104
|
+
className={cn("font-semibold text-foreground", className)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function SheetDescription({
|
|
111
|
+
className,
|
|
112
|
+
...props
|
|
113
|
+
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
|
114
|
+
return (
|
|
115
|
+
<SheetPrimitive.Description
|
|
116
|
+
data-slot="sheet-description"
|
|
117
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export {
|
|
124
|
+
Sheet,
|
|
125
|
+
SheetTrigger,
|
|
126
|
+
SheetClose,
|
|
127
|
+
SheetContent,
|
|
128
|
+
SheetHeader,
|
|
129
|
+
SheetFooter,
|
|
130
|
+
SheetTitle,
|
|
131
|
+
SheetDescription,
|
|
132
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
/* THE APP TAB. The kernel index's dense object: 32px identity mark on a flat
|
|
4
|
+
--muted panel (the colour the textures measured as: dL −0.027, C 1.25x vs
|
|
5
|
+
ground), name over optional caption, meta right. Hover is 5% ink over muted
|
|
6
|
+
(Δ13 levels, --accent's Δ5 vanishes). Promoted from the shell 2026-08-18. */
|
|
7
|
+
|
|
8
|
+
export function AppTab({
|
|
9
|
+
mark,
|
|
10
|
+
icon,
|
|
11
|
+
name,
|
|
12
|
+
caption,
|
|
13
|
+
meta,
|
|
14
|
+
tag,
|
|
15
|
+
onClick,
|
|
16
|
+
onContextMenu,
|
|
17
|
+
}: {
|
|
18
|
+
/** 32px identity square, e.g. the register's slice art on a style prop. */
|
|
19
|
+
mark?: React.CSSProperties;
|
|
20
|
+
/** The app's chosen emoji, drawn INSIDE the mark. The mark is derived
|
|
21
|
+
provenance, a colour that says which project this came from, so the icon
|
|
22
|
+
layers over it rather than replacing it: an app keeps its family resemblance
|
|
23
|
+
and gains an identity of its own. */
|
|
24
|
+
icon?: string;
|
|
25
|
+
name: string;
|
|
26
|
+
/** Dropped when it would restate its context (section, filter, or the name). */
|
|
27
|
+
caption?: string;
|
|
28
|
+
meta?: string;
|
|
29
|
+
tag?: string;
|
|
30
|
+
onClick?: () => void;
|
|
31
|
+
onContextMenu?: (e: React.MouseEvent) => void;
|
|
32
|
+
}) {
|
|
33
|
+
return (
|
|
34
|
+
<button
|
|
35
|
+
data-part={tag}
|
|
36
|
+
type="button"
|
|
37
|
+
onClick={onClick}
|
|
38
|
+
onContextMenu={onContextMenu}
|
|
39
|
+
className="group scale-chrome flex w-full items-center gap-2.5 rounded-[9px] bg-muted px-2.5 py-2.5 text-left transition-colors hover:bg-[color-mix(in_oklab,var(--foreground)_5%,var(--muted))]"
|
|
40
|
+
>
|
|
41
|
+
<span
|
|
42
|
+
className="relative grid size-8 shrink-0 place-items-center overflow-hidden rounded-[6px]"
|
|
43
|
+
style={{
|
|
44
|
+
border: "1px solid color-mix(in oklab, var(--foreground) 8%, transparent)",
|
|
45
|
+
...mark,
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
{icon && <span className="text-[15px] leading-none">{icon}</span>}
|
|
49
|
+
</span>
|
|
50
|
+
<span className="min-w-0 flex-1">
|
|
51
|
+
<span className="block truncate text-body font-medium leading-tight text-foreground">
|
|
52
|
+
{name}
|
|
53
|
+
</span>
|
|
54
|
+
{caption && (
|
|
55
|
+
<span className="block truncate text-caption leading-tight text-muted-foreground">
|
|
56
|
+
{caption}
|
|
57
|
+
</span>
|
|
58
|
+
)}
|
|
59
|
+
</span>
|
|
60
|
+
{meta && (
|
|
61
|
+
<span className="shrink-0 text-caption tabular-nums leading-none text-muted-foreground">
|
|
62
|
+
{meta}
|
|
63
|
+
</span>
|
|
64
|
+
)}
|
|
65
|
+
</button>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Button } from "../button";
|
|
3
|
+
|
|
4
|
+
/* THE WORKING-SET ENTRY. The desktop kernel's list row: identity mark (art via
|
|
5
|
+
prop), name, and the three state channels (dim dot = attached, brand dot =
|
|
6
|
+
waiting on you, pulsing mark-ring = agent working). Promoted from the shell
|
|
7
|
+
2026-08-18; data-part attributes are plain strings (no canvas coupling). */
|
|
8
|
+
|
|
9
|
+
export type Entry = {
|
|
10
|
+
/** The mark's ground, e.g. the register's slice art. Supplied by the app. */
|
|
11
|
+
art?: React.CSSProperties;
|
|
12
|
+
slug: string;
|
|
13
|
+
name: string;
|
|
14
|
+
/** The repo/dir this app lives under, revealed on hover of the mark. */
|
|
15
|
+
root: string;
|
|
16
|
+
/** Content colour. Absent = an INFERRED root: neutral, no colour. */
|
|
17
|
+
mark?: string;
|
|
18
|
+
/** A session is attached to this route. */
|
|
19
|
+
live?: boolean;
|
|
20
|
+
/** An agent is working in it right now. */
|
|
21
|
+
agent?: boolean;
|
|
22
|
+
/** Unread events waiting on you. */
|
|
23
|
+
notes?: number;
|
|
24
|
+
on?: boolean;
|
|
25
|
+
pinned?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/* A real root wears the user's colour; an inferred one wears a neutral outline.
|
|
29
|
+
The difference is legible without a legend because one has colour and one
|
|
30
|
+
doesn't, in a single 18px square. */
|
|
31
|
+
function RootMark({ entry }: { entry: Entry }) {
|
|
32
|
+
return (
|
|
33
|
+
<span className="relative flex">
|
|
34
|
+
{/* The apps index's hash-sliced art, same source, same treatment. */}
|
|
35
|
+
<span
|
|
36
|
+
className="size-[18px] shrink-0 overflow-hidden rounded-[5px] border border-border"
|
|
37
|
+
style={entry.art ?? undefined}
|
|
38
|
+
/>
|
|
39
|
+
{entry.agent && (
|
|
40
|
+
<span
|
|
41
|
+
aria-label="agent working"
|
|
42
|
+
className="pointer-events-none absolute -inset-[3px] animate-pulse rounded-[8px] border border-[var(--brand)]"
|
|
43
|
+
/>
|
|
44
|
+
)}
|
|
45
|
+
</span>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function EntryRow({
|
|
50
|
+
entry,
|
|
51
|
+
forceHover,
|
|
52
|
+
showPinSlot,
|
|
53
|
+
}: {
|
|
54
|
+
entry: Entry;
|
|
55
|
+
forceHover?: boolean;
|
|
56
|
+
showPinSlot?: boolean;
|
|
57
|
+
}) {
|
|
58
|
+
return (
|
|
59
|
+
<Button
|
|
60
|
+
data-part={`app-${entry.slug}`}
|
|
61
|
+
variant="ghost"
|
|
62
|
+
size="sm"
|
|
63
|
+
className={
|
|
64
|
+
/* The row's 13px is EXACTLY --type-title under .scale-chrome, but it
|
|
65
|
+
cannot be spelled `text-title` here: this className goes through
|
|
66
|
+
shadcn's `cn()`, and tailwind-merge parses an unknown `text-<name>`
|
|
67
|
+
as a text-COLOR utility, so `text-title` is dropped outright by the
|
|
68
|
+
`text-muted-foreground` / `text-accent-foreground` that follows it,
|
|
69
|
+
leaving Button's own `text-sm` (14px) to win. Measured. The scale
|
|
70
|
+
utilities are only safe on elements that do NOT go through `cn()`
|
|
71
|
+
until tailwind-merge is taught the register's font-size keys. */
|
|
72
|
+
"scale-chrome relative w-full justify-start gap-2.5 text-[13px] font-normal " +
|
|
73
|
+
(entry.on ? "bg-accent text-accent-foreground" : "text-muted-foreground")
|
|
74
|
+
}
|
|
75
|
+
>
|
|
76
|
+
{/* `group/mark` scopes the hover to the MARK: a row-wide hover would fire
|
|
77
|
+
the root name every time you were merely on your way to clicking the
|
|
78
|
+
app. */}
|
|
79
|
+
<span className="group/mark relative shrink-0 flex items-center">
|
|
80
|
+
<RootMark entry={entry} />
|
|
81
|
+
<span
|
|
82
|
+
data-part={`root-${entry.slug}`}
|
|
83
|
+
className={
|
|
84
|
+
/* BELOW the mark, not beside it. Beside MEASURED wrong: at 240px the
|
|
85
|
+
only space to a left-edge mark's right is the app name, so the chip
|
|
86
|
+
covered the first characters of the row you were pointing at and
|
|
87
|
+
read as a rendering fault rather than a tooltip. Below-left is also
|
|
88
|
+
simply what Tooltip does by default, which is the tell that the
|
|
89
|
+
beside-placement was fighting the pattern rather than adapting it. */
|
|
90
|
+
"pointer-events-none absolute left-0 top-[calc(100%+6px)] z-10 " +
|
|
91
|
+
"whitespace-nowrap rounded-[6px] border px-2 py-1 text-caption leading-none " +
|
|
92
|
+
"bg-popover text-popover-foreground shadow-lg transition-opacity " +
|
|
93
|
+
(forceHover ? "opacity-100" : "opacity-0 group-hover/mark:opacity-100")
|
|
94
|
+
}
|
|
95
|
+
>
|
|
96
|
+
{entry.root}
|
|
97
|
+
</span>
|
|
98
|
+
</span>
|
|
99
|
+
{/* Pinned is NEUTRAL: a glyph, not a colour and not a fill. It marks
|
|
100
|
+
how the row GOT here, you put it there, which is a different axis
|
|
101
|
+
from what-you-owe-vs-what-owes-you, and both can be true. */}
|
|
102
|
+
{/* A RESERVED SLOT, present on every row, occupied only when pinned.
|
|
103
|
+
MEASURED without it: a pinned row's name started at x 78 and an
|
|
104
|
+
unpinned one at x 52, 26px of ragged left edge down the list, which is
|
|
105
|
+
the same defect an earlier kind token was built to avoid and this
|
|
106
|
+
reintroduced one component over. The cost is honest and worth naming:
|
|
107
|
+
~14px of name width spent on EVERY row to keep the column straight, on
|
|
108
|
+
a rail whose longest real name already needs 232 of 240.
|
|
109
|
+
The alternative, a pin badge on the project mark, costs nothing
|
|
110
|
+
horizontally and is rejected on different grounds: at 18px that square
|
|
111
|
+
already carries a gradient and (collapsed) a liveness dot, and a later
|
|
112
|
+
round measured that small marks stop reading once they carry three
|
|
113
|
+
signals. */}
|
|
114
|
+
{showPinSlot && (
|
|
115
|
+
<span className="shrink-0 w-[11px] flex items-center justify-center">
|
|
116
|
+
{entry.pinned && (
|
|
117
|
+
<span aria-label="pinned" className="size-[5px] rounded-full bg-current opacity-70" />
|
|
118
|
+
)}
|
|
119
|
+
</span>
|
|
120
|
+
)}
|
|
121
|
+
<span className="truncate">{entry.name}</span>
|
|
122
|
+
{/* One slot, one geometry, two weights: brand = something waits on you,
|
|
123
|
+
dim = merely attached. */}
|
|
124
|
+
{entry.notes ? (
|
|
125
|
+
<span
|
|
126
|
+
aria-label="waiting"
|
|
127
|
+
className="ml-auto shrink-0 size-[5px] rounded-full bg-[var(--brand)]"
|
|
128
|
+
/>
|
|
129
|
+
) : entry.live ? (
|
|
130
|
+
<span
|
|
131
|
+
aria-label="connected"
|
|
132
|
+
className="ml-auto shrink-0 size-[5px] rounded-full bg-current opacity-35"
|
|
133
|
+
/>
|
|
134
|
+
) : null}
|
|
135
|
+
</Button>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Badge } from "../badge";
|
|
3
|
+
|
|
4
|
+
/* THE FIELD FAMILY. The kernel bar's centre slot: one field shell so its two
|
|
5
|
+
tenancies (address / search) can never drift on geometry, the queue plate
|
|
6
|
+
(count + ⌘⏎ chord, sans on purpose, mono ⌘ floats off-baseline), and the
|
|
7
|
+
⌘K hint it evicts. Promoted from the shell 2026-08-18. */
|
|
8
|
+
|
|
9
|
+
const FIELD = {
|
|
10
|
+
background: "color-mix(in oklab, var(--paper) 10%, transparent)",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function Field({
|
|
14
|
+
children,
|
|
15
|
+
width = 420,
|
|
16
|
+
}: {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
width?: number | string;
|
|
19
|
+
}) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
data-part="address-field"
|
|
23
|
+
className="scale-chrome h-[30px] rounded-[8px] flex items-center px-2.5 gap-2.5"
|
|
24
|
+
style={{ ...FIELD, width }}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* v13's StateSlot at the new height. Same rule, unchanged: a queue evicts the
|
|
32
|
+
hint, and the two-digit column is reserved so the plate never changes width
|
|
33
|
+
between 1 and 12. */
|
|
34
|
+
export function StateSlot({ count, onSend }: { count: number; onSend?: () => void }) {
|
|
35
|
+
if (count > 0) {
|
|
36
|
+
return (
|
|
37
|
+
<Badge
|
|
38
|
+
data-part="queue-slot"
|
|
39
|
+
asChild
|
|
40
|
+
variant="default"
|
|
41
|
+
className="scale-chrome ml-auto shrink-0 h-[17px] px-1.5 py-0"
|
|
42
|
+
>
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
onClick={onSend}
|
|
46
|
+
aria-label={`Send ${count} to session, Command Return`}
|
|
47
|
+
title="Send to session, ⌘⏎"
|
|
48
|
+
className="flex items-center gap-1"
|
|
49
|
+
>
|
|
50
|
+
<span
|
|
51
|
+
data-part="queue-count"
|
|
52
|
+
className="text-meta font-semibold leading-none tabular-nums text-center"
|
|
53
|
+
style={{ minWidth: 13 }}
|
|
54
|
+
>
|
|
55
|
+
{count}
|
|
56
|
+
</span>
|
|
57
|
+
{/* NOT font-mono, that is the "better key shortcut stuff": the
|
|
58
|
+
⌘K hint already measured that every mono face on this box ships a
|
|
59
|
+
small, baseline-floating ⌘ (JetBrains 6.25px, descent −1.05) while
|
|
60
|
+
the sans stack renders it at cap height sitting on the baseline.
|
|
61
|
+
An earlier pass set this chord in mono and would have reintroduced
|
|
62
|
+
the exact defect a later fix addressed. SEMIBOLD is that fix's
|
|
63
|
+
finding and it holds: weight, not size, is what keeps the ⏎ from
|
|
64
|
+
collapsing to a tick. */}
|
|
65
|
+
<span data-part="queue-chord" className="text-meta font-semibold leading-none opacity-65">
|
|
66
|
+
⌘⏎
|
|
67
|
+
</span>
|
|
68
|
+
</button>
|
|
69
|
+
</Badge>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return <KbdHint />;
|
|
73
|
+
}
|
|
74
|
+
export function KbdHint() {
|
|
75
|
+
return (
|
|
76
|
+
<span
|
|
77
|
+
data-part="kbd-hint"
|
|
78
|
+
className="scale-chrome ml-auto shrink-0 text-caption leading-none text-muted-foreground tracking-[0.06em]"
|
|
79
|
+
>
|
|
80
|
+
⌘K
|
|
81
|
+
</span>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* THE SECTION HEAD. A header is a word, so it has to earn its place. It does,
|
|
2
|
+
because a grouped list creates exactly two questions the layout
|
|
3
|
+
cannot answer on its own: which group am I in, and how much is in here. So the
|
|
4
|
+
label and the count, and nothing else. No rule, no chevron, no "N apps": the
|
|
5
|
+
grid below already draws the section's own left edge and the tabs already say
|
|
6
|
+
what they are.
|
|
7
|
+
|
|
8
|
+
Set in the tab's own name type (12.5px medium) rather than a smaller uppercase
|
|
9
|
+
micro-label, because a section head and an app name are peers in the hierarchy
|
|
10
|
+
here. One names a place, one names a thing in it, and neither outranks the
|
|
11
|
+
other. The count drops to the caption size to stay subordinate to its label. */
|
|
12
|
+
export function GroupHead({ label, count }: { label: string; count: number }) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="scale-chrome flex items-baseline gap-2 pb-1.5 pt-2">
|
|
15
|
+
<span className="text-body font-medium leading-none text-foreground">{label}</span>
|
|
16
|
+
<span className="text-caption leading-none tabular-nums text-muted-foreground">{count}</span>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* THE SCOPE CHIP. The kernel's filter chip: lit = ink inversion (a two-state
|
|
2
|
+
control read in peripheral vision; a tint would sit inside the resting band).
|
|
3
|
+
Tap the lit chip again to clear, a reset needs to exist, not a word.
|
|
4
|
+
Promoted from the shell 2026-08-18. */
|
|
5
|
+
|
|
6
|
+
export function ScopeChip({
|
|
7
|
+
label,
|
|
8
|
+
on,
|
|
9
|
+
onClick,
|
|
10
|
+
tag,
|
|
11
|
+
}: {
|
|
12
|
+
label: string;
|
|
13
|
+
on: boolean;
|
|
14
|
+
onClick: () => void;
|
|
15
|
+
tag?: string;
|
|
16
|
+
}) {
|
|
17
|
+
return (
|
|
18
|
+
<button
|
|
19
|
+
data-part={tag}
|
|
20
|
+
type="button"
|
|
21
|
+
onClick={onClick}
|
|
22
|
+
className={
|
|
23
|
+
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11.5px] leading-none transition-colors " +
|
|
24
|
+
(on
|
|
25
|
+
? "bg-foreground text-background"
|
|
26
|
+
: "bg-muted text-foreground hover:bg-[color-mix(in_oklab,var(--foreground)_5%,var(--muted))]")
|
|
27
|
+
}
|
|
28
|
+
>
|
|
29
|
+
<span className="font-medium">{label}</span>
|
|
30
|
+
</button>
|
|
31
|
+
);
|
|
32
|
+
}
|