@yourrealm/sdk 0.0.0-alpha.1378.ce03c8dc
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-EXCEPTION +26 -0
- package/README.md +34 -0
- package/dist/chunk-3R6KEFOI.js +22 -0
- package/dist/chunk-67BDAMYX.js +376 -0
- package/dist/chunk-AITGS27R.js +139 -0
- package/dist/chunk-CMAEHQ4F.js +6 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-O3UIK34B.js +1430 -0
- package/dist/chunk-VHTUSZUX.js +176 -0
- package/dist/cli.js +9006 -0
- package/dist/docker.js +54 -0
- package/dist/index.js +1053 -0
- package/dist/ingress.js +436 -0
- package/dist/jsx-runtime.js +2 -0
- package/dist/preact.js +22 -0
- package/dist/service-url.js +11 -0
- package/dist/services.js +213 -0
- package/dist/test.js +5069 -0
- package/dist/ui.js +254 -0
- package/dist/worker-protocol.js +89 -0
- package/package.json +112 -0
- package/src/app-icon.ts +74 -0
- package/src/apply.ts +52 -0
- package/src/bundle-ui.ts +211 -0
- package/src/bundle.ts +546 -0
- package/src/cli/build-dev-landing.ts +84 -0
- package/src/cli/build-install-panel.ts +90 -0
- package/src/cli/build-runtime.ts +133 -0
- package/src/cli/build.ts +63 -0
- package/src/cli/dev.ts +107 -0
- package/src/cli/down.ts +60 -0
- package/src/cli/main.ts +56 -0
- package/src/cli/test.ts +61 -0
- package/src/cli/validate.ts +37 -0
- package/src/compile.ts +100 -0
- package/src/create-app.ts +982 -0
- package/src/deep-merge.ts +33 -0
- package/src/dev/fixture.ts +48 -0
- package/src/dev/install-handler.ts +383 -0
- package/src/dev/server.ts +968 -0
- package/src/dev/stack.ts +108 -0
- package/src/dev/state.ts +159 -0
- package/src/docker.ts +96 -0
- package/src/errors.ts +61 -0
- package/src/factory.ts +481 -0
- package/src/garage.ts +56 -0
- package/src/generator/hash.ts +6 -0
- package/src/generator/resolve-refs.ts +207 -0
- package/src/grants-builder.ts +100 -0
- package/src/grants.ts +260 -0
- package/src/home-api.ts +172 -0
- package/src/home-cookie.ts +10 -0
- package/src/home-registry.ts +120 -0
- package/src/home.ts +187 -0
- package/src/host-registry.ts +143 -0
- package/src/http-header.ts +20 -0
- package/src/identifiers.ts +49 -0
- package/src/index.ts +66 -0
- package/src/info.ts +64 -0
- package/src/ini.ts +50 -0
- package/src/json.ts +14 -0
- package/src/jsx-runtime/index.ts +5 -0
- package/src/keys.ts +29 -0
- package/src/mysql.ts +56 -0
- package/src/pack-build.ts +108 -0
- package/src/pack.ts +115 -0
- package/src/peer-registry.ts +121 -0
- package/src/peer-world.ts +117 -0
- package/src/permission-meta.ts +116 -0
- package/src/permissions.ts +108 -0
- package/src/pkg-root.ts +34 -0
- package/src/postgres.ts +79 -0
- package/src/preact/index.ts +27 -0
- package/src/redis.ts +43 -0
- package/src/ref.ts +271 -0
- package/src/remote-registry.ts +33 -0
- package/src/required-host-kinds.ts +110 -0
- package/src/resolve-context.ts +111 -0
- package/src/resolve.ts +471 -0
- package/src/runtime/docker/compose/compose-schema.ts +118 -0
- package/src/runtime/docker/compose/convert-to-docker-compose.ts +278 -0
- package/src/runtime/docker/compose/default-hardening.ts +21 -0
- package/src/runtime/docker/compose/effects.ts +107 -0
- package/src/runtime/docker/compose/index.ts +17 -0
- package/src/runtime/docker/compose/labels.ts +39 -0
- package/src/runtime/docker/compose/types.ts +78 -0
- package/src/runtime/docker/home-proxy-server/index.ts +177 -0
- package/src/runtime/docker/home-proxy-server/protocol.ts +53 -0
- package/src/runtime/docker/ingress/build-config.ts +819 -0
- package/src/runtime/docker/ingress/index.ts +16 -0
- package/src/runtime/docker/ingress/types.ts +182 -0
- package/src/runtime/docker/log-frames.ts +121 -0
- package/src/runtime/docker/logs-server/index.ts +366 -0
- package/src/runtime/docker/spawn-server/context-key.ts +82 -0
- package/src/runtime/docker/spawn-server/index.ts +492 -0
- package/src/runtime/docker/spawn-server/protocol.ts +70 -0
- package/src/runtime/docker/spawn-server/spec.ts +122 -0
- package/src/runtime/docker/stats-server/disks.ts +161 -0
- package/src/runtime/docker/stats-server/index.ts +322 -0
- package/src/runtime/home-client/index.ts +71 -0
- package/src/runtime/log-line.ts +24 -0
- package/src/runtime/logs-client/index.ts +175 -0
- package/src/runtime/spawn-client/index.ts +93 -0
- package/src/runtime/spawn-client/kaniko.ts +79 -0
- package/src/runtime/stats-client/index.ts +65 -0
- package/src/runtime/stats-sample.ts +95 -0
- package/src/safe-json.ts +37 -0
- package/src/sandbox.ts +1209 -0
- package/src/schema/build-apps.ts +38 -0
- package/src/schema/home.ts +217 -0
- package/src/schema/host.ts +792 -0
- package/src/schema/inputs.ts +26 -0
- package/src/schema/nodes.ts +75 -0
- package/src/schema/peer.ts +400 -0
- package/src/schema/public-domain.ts +116 -0
- package/src/schema/remote.ts +74 -0
- package/src/security.ts +594 -0
- package/src/serialize.ts +21 -0
- package/src/service-definition.ts +284 -0
- package/src/service-url.ts +64 -0
- package/src/service.ts +39 -0
- package/src/services/index.ts +5 -0
- package/src/shared.ts +65 -0
- package/src/speculative-silo.ts +96 -0
- package/src/stable-secret.ts +14 -0
- package/src/surfaces.ts +264 -0
- package/src/tailwind-bin.ts +56 -0
- package/src/test/boot.ts +192 -0
- package/src/test/compile.ts +501 -0
- package/src/test/describe.ts +117 -0
- package/src/test/index.ts +11 -0
- package/src/test/ingress-fixture.ts +37 -0
- package/src/test/types.ts +318 -0
- package/src/trusted-headers.ts +79 -0
- package/src/types.ts +126 -0
- package/src/ui/components/aside.tsx +96 -0
- package/src/ui/components/badge.tsx +42 -0
- package/src/ui/components/button.tsx +59 -0
- package/src/ui/components/card.tsx +106 -0
- package/src/ui/components/checkbox.tsx +37 -0
- package/src/ui/components/code-block.tsx +29 -0
- package/src/ui/components/combobox.tsx +119 -0
- package/src/ui/components/config-field.tsx +284 -0
- package/src/ui/components/dialog.tsx +137 -0
- package/src/ui/components/domain-editor.tsx +317 -0
- package/src/ui/components/domain-list.ts +205 -0
- package/src/ui/components/dropdown-menu.tsx +73 -0
- package/src/ui/components/empty-state.tsx +51 -0
- package/src/ui/components/highlight.ts +59 -0
- package/src/ui/components/icons.tsx +101 -0
- package/src/ui/components/image-posture.tsx +85 -0
- package/src/ui/components/input.tsx +28 -0
- package/src/ui/components/label.tsx +16 -0
- package/src/ui/components/line-chart.tsx +113 -0
- package/src/ui/components/page.tsx +39 -0
- package/src/ui/components/permission-row.tsx +262 -0
- package/src/ui/components/permission-section.tsx +51 -0
- package/src/ui/components/pie-chart.tsx +245 -0
- package/src/ui/components/schema-block.tsx +60 -0
- package/src/ui/components/separator.tsx +42 -0
- package/src/ui/components/sheet.tsx +130 -0
- package/src/ui/components/sidebar.tsx +650 -0
- package/src/ui/components/silo-picker.tsx +510 -0
- package/src/ui/components/skeleton.tsx +13 -0
- package/src/ui/components/sonner.tsx +33 -0
- package/src/ui/components/switch.tsx +58 -0
- package/src/ui/components/table-view.ts +219 -0
- package/src/ui/components/table.tsx +506 -0
- package/src/ui/components/tabs.tsx +59 -0
- package/src/ui/components/timeago.tsx +84 -0
- package/src/ui/components/tooltip.tsx +56 -0
- package/src/ui/components.json +21 -0
- package/src/ui/dev/dev-shell.tsx +145 -0
- package/src/ui/home/home-grid.tsx +177 -0
- package/src/ui/hooks/use-mobile.ts +19 -0
- package/src/ui/index.ts +189 -0
- package/src/ui/install/adapter.ts +193 -0
- package/src/ui/install/deploy-targets-picker.tsx +185 -0
- package/src/ui/install/install-app-panel.tsx +912 -0
- package/src/ui/install/install-error.tsx +62 -0
- package/src/ui/install/perms-inactive.ts +50 -0
- package/src/ui/install/required-fields.ts +37 -0
- package/src/ui/install/tabs-form.tsx +557 -0
- package/src/ui/tokens.css +279 -0
- package/src/ui/utils.ts +78 -0
- package/src/ui-error.ts +139 -0
- package/src/ui-runtime/dev-landing-entry.tsx +258 -0
- package/src/ui-runtime/index.ts +83 -0
- package/src/ui-runtime/install-entry.tsx +119 -0
- package/src/ui-runtime/mount.ts +367 -0
- package/src/ui-runtime/runtime.css +56 -0
- package/src/ui-surface.ts +451 -0
- package/src/valkey.ts +43 -0
- package/src/worker/extract-tar.ts +125 -0
- package/src/worker/identity.ts +183 -0
- package/src/worker/index.ts +16 -0
- package/src/worker/public-ip.ts +92 -0
- package/src/worker/queue.ts +54 -0
- package/src/worker/reconcile.ts +127 -0
- package/src/worker/start-worker.ts +227 -0
- package/src/worker/ui-proxy.ts +335 -0
- package/src/worker/uplink.ts +523 -0
- package/src/worker/validate.ts +34 -0
- package/src/worker/verify-relay.ts +102 -0
- package/src/worker-protocol/app-dirs.ts +94 -0
- package/src/worker-protocol/home-proxy-paths.ts +76 -0
- package/src/worker-protocol/index.ts +55 -0
- package/src/worker-protocol/ingress.ts +166 -0
- package/src/worker-protocol/logs-socket-path.ts +50 -0
- package/src/worker-protocol/networks.ts +39 -0
- package/src/worker-protocol/node-version.ts +70 -0
- package/src/worker-protocol/silo-paths.ts +114 -0
- package/src/worker-protocol/spawn-paths.ts +165 -0
- package/src/worker-protocol/stats-socket-path.ts +40 -0
- package/src/worker-protocol/update-files.ts +77 -0
- package/src/worker-protocol/uplink/constants.ts +86 -0
- package/src/worker-protocol/uplink/errors.ts +61 -0
- package/src/worker-protocol/uplink/frame.ts +114 -0
- package/src/worker-protocol/uplink/identity.ts +40 -0
- package/src/worker-protocol/uplink/index.ts +63 -0
- package/src/worker-protocol/uplink/methods.ts +462 -0
- package/src/worker-protocol/uplink/mux.ts +646 -0
- package/src/worker-protocol/uplink/protocol.ts +11 -0
- package/src/worker-protocol/uplink/rpc.ts +309 -0
- package/src/worker-protocol/uplink/session.ts +540 -0
- package/src/worker-protocol/uplink/test-support.ts +188 -0
- package/src/worker-protocol/worker-mount.ts +27 -0
- package/src/worker-protocol/worker-state.ts +190 -0
- package/src/xml.ts +49 -0
- package/src/yaml.ts +15 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
import { cn, type ElementProps } from "../utils.ts";
|
|
4
|
+
|
|
5
|
+
const buttonVariants = cva(
|
|
6
|
+
"cursor-pointer group/button inline-flex shrink-0 items-center justify-center text-sm font-bold whitespace-nowrap transition-all outline-none select-none focus-visible:ring-3 focus-visible:ring-ring/50 active:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default:
|
|
11
|
+
"bg-primary text-primary-foreground shadow-clay-primary hover:brightness-110 dark:shadow-clay-dark-primary",
|
|
12
|
+
outline:
|
|
13
|
+
"border-border bg-background shadow-sm hover:shadow hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:shadow-clay-dark-sm dark:hover:shadow-clay-dark",
|
|
14
|
+
secondary:
|
|
15
|
+
"bg-surface-light text-foreground shadow-sm hover:shadow aria-expanded:bg-surface-light aria-expanded:text-foreground dark:bg-secondary dark:shadow-clay-dark-sm dark:hover:shadow-clay-dark",
|
|
16
|
+
ghost:
|
|
17
|
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
18
|
+
destructive:
|
|
19
|
+
"bg-destructive/10 text-destructive shadow-clay-danger hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
20
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
21
|
+
},
|
|
22
|
+
size: {
|
|
23
|
+
default:
|
|
24
|
+
"gap-1.5 rounded-[10px] px-4 py-2 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
|
25
|
+
xs: "gap-1 rounded-lg px-2.5 py-1 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
26
|
+
sm: "gap-1 rounded-[10px] px-3 py-1.5 text-[0.8rem] has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3.5",
|
|
27
|
+
lg: "gap-2 rounded-2xl px-6 py-3.5 has-data-[icon=inline-end]:pr-4 has-data-[icon=inline-start]:pl-4",
|
|
28
|
+
xl: "gap-2 rounded-xl px-9 py-4 text-[15px] has-data-[icon=inline-end]:pr-6 has-data-[icon=inline-start]:pl-6",
|
|
29
|
+
icon: "size-9 rounded-[10px]",
|
|
30
|
+
"icon-xs": "size-6 rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
31
|
+
"icon-sm": "size-7 rounded-[10px]",
|
|
32
|
+
"icon-lg": "size-11 rounded-xl",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
variant: "default",
|
|
37
|
+
size: "default",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
function Button({
|
|
43
|
+
className,
|
|
44
|
+
variant = "default",
|
|
45
|
+
size = "default",
|
|
46
|
+
type = "button",
|
|
47
|
+
...props
|
|
48
|
+
}: ElementProps<"button"> & VariantProps<typeof buttonVariants>) {
|
|
49
|
+
return (
|
|
50
|
+
<button
|
|
51
|
+
data-slot="button"
|
|
52
|
+
type={type}
|
|
53
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
import { cn, type ElementProps } from "../utils.ts";
|
|
4
|
+
|
|
5
|
+
const cardVariants = cva(
|
|
6
|
+
"group/card flex flex-col gap-4 overflow-hidden text-card-foreground has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default:
|
|
11
|
+
"rounded-2xl bg-card px-2 py-4 text-sm shadow *:[img:first-child]:rounded-t-2xl *:[img:last-child]:rounded-b-2xl data-[size=sm]:py-3",
|
|
12
|
+
glass: "rounded-3xl bg-card/35 backdrop-blur-xl shadow-xl",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
variant: "default",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
type CardProps = ElementProps<"div"> &
|
|
22
|
+
VariantProps<typeof cardVariants> & {
|
|
23
|
+
size?: "default" | "sm";
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function Card({ className, size = "default", variant, ...props }: CardProps) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
data-slot="card"
|
|
30
|
+
data-size={size}
|
|
31
|
+
className={cn(cardVariants({ variant, className }))}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function CardHeader({ className, ...props }: ElementProps<"div">) {
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
data-slot="card-header"
|
|
41
|
+
className={cn(
|
|
42
|
+
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-2xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function CardTitle({ className, ...props }: ElementProps<"div">) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
data-slot="card-title"
|
|
54
|
+
className={cn(
|
|
55
|
+
"text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function CardDescription({ className, ...props }: ElementProps<"div">) {
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
data-slot="card-description"
|
|
67
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function CardAction({ className, ...props }: ElementProps<"div">) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="card-action"
|
|
77
|
+
className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function CardContent({ className, ...props }: ElementProps<"div">) {
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
data-slot="card-content"
|
|
87
|
+
className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
|
|
88
|
+
{...props}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function CardFooter({ className, ...props }: ElementProps<"div">) {
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
data-slot="card-footer"
|
|
97
|
+
className={cn(
|
|
98
|
+
"flex items-center rounded-b-2xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3",
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ComponentProps } from "preact";
|
|
2
|
+
import { Checkbox as CheckboxPrimitive } from "@ark-ui/react/checkbox";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../utils.ts";
|
|
5
|
+
import { CheckBoldIcon } from "./icons.tsx";
|
|
6
|
+
|
|
7
|
+
function Checkbox({
|
|
8
|
+
className,
|
|
9
|
+
id,
|
|
10
|
+
...props
|
|
11
|
+
}: ComponentProps<typeof CheckboxPrimitive.Root> & { id?: string }) {
|
|
12
|
+
return (
|
|
13
|
+
<CheckboxPrimitive.Root
|
|
14
|
+
ids={id ? { hiddenInput: id } : undefined}
|
|
15
|
+
data-slot="checkbox"
|
|
16
|
+
className={cn(
|
|
17
|
+
// The root is a <label>; focus lands on the hidden input, so the ring
|
|
18
|
+
// has to key off the `data-focus-visible` Zag mirrors onto every part.
|
|
19
|
+
"cursor-pointer peer relative flex size-4 shrink-0 items-center justify-center rounded-[5px] border border-border bg-card shadow-sm transition-all outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 data-[focus-visible]:ring-3 data-[focus-visible]:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:bg-input/30 dark:shadow-clay-dark-sm dark:aria-invalid:ring-destructive/40 data-[state=checked]:bg-primary data-[state=checked]:shadow-clay-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary dark:data-[state=checked]:shadow-clay-dark-primary",
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
<CheckboxPrimitive.Control
|
|
25
|
+
data-slot="checkbox-indicator"
|
|
26
|
+
className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
|
|
27
|
+
>
|
|
28
|
+
<CheckboxPrimitive.Indicator>
|
|
29
|
+
<CheckBoldIcon />
|
|
30
|
+
</CheckboxPrimitive.Indicator>
|
|
31
|
+
</CheckboxPrimitive.Control>
|
|
32
|
+
<CheckboxPrimitive.HiddenInput />
|
|
33
|
+
</CheckboxPrimitive.Root>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { Checkbox };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Pure presentational component: renders pre-highlighted HTML. The Shiki-backed
|
|
2
|
+
// highlightCode() lives in ./highlight.ts so this stays client-island-safe (no
|
|
3
|
+
// Shiki/WASM in the bundle). See that file for why.
|
|
4
|
+
export function CodeBlock({
|
|
5
|
+
html,
|
|
6
|
+
filename,
|
|
7
|
+
className,
|
|
8
|
+
}: {
|
|
9
|
+
html: string;
|
|
10
|
+
filename?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="bg-text-dark rounded-2xl shadow-clay-dark overflow-hidden">
|
|
15
|
+
{filename && (
|
|
16
|
+
<div className="flex items-center gap-2 px-4 py-2.5 border-b border-white/10">
|
|
17
|
+
<div className="w-2.5 h-2.5 rounded-full bg-red-400/60" />
|
|
18
|
+
<div className="w-2.5 h-2.5 rounded-full bg-yellow-400/60" />
|
|
19
|
+
<div className="w-2.5 h-2.5 rounded-full bg-green-400/60" />
|
|
20
|
+
<span className="ml-2 text-xs text-white/30 font-mono">{filename}</span>
|
|
21
|
+
</div>
|
|
22
|
+
)}
|
|
23
|
+
<pre
|
|
24
|
+
className={`font-mono text-xs leading-relaxed p-5 m-0 overflow-x-auto ${className ?? ""}`}
|
|
25
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Combobox as ComboboxPrimitive,
|
|
3
|
+
type ComboboxInputValueChangeDetails,
|
|
4
|
+
type ComboboxValueChangeDetails,
|
|
5
|
+
useListCollection,
|
|
6
|
+
} from "@ark-ui/react/combobox";
|
|
7
|
+
import { useFilter } from "@ark-ui/react/locale";
|
|
8
|
+
import { useEffect } from "preact/hooks";
|
|
9
|
+
|
|
10
|
+
import { cn } from "../utils.ts";
|
|
11
|
+
import { CheckIcon, MagnifyingGlassIcon } from "./icons.tsx";
|
|
12
|
+
|
|
13
|
+
// One selectable option. `label` is what shows + what typing filters against;
|
|
14
|
+
// `value` is what `onValueChange` reports.
|
|
15
|
+
interface ComboboxOption {
|
|
16
|
+
value: string;
|
|
17
|
+
label: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ComboboxProps {
|
|
21
|
+
items: ComboboxOption[];
|
|
22
|
+
value?: string;
|
|
23
|
+
onValueChange?: (value: string) => void;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
emptyText?: string;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// A searchable single-select on Ark UI's Combobox. Ark drives filtering through
|
|
31
|
+
// a collection, so this owns an input-value filter and re-renders the list from
|
|
32
|
+
// it. Self-contained: pass `items` and it renders the search input, the filtered
|
|
33
|
+
// list, and a check on the selected row.
|
|
34
|
+
export function Combobox({
|
|
35
|
+
items,
|
|
36
|
+
value,
|
|
37
|
+
onValueChange,
|
|
38
|
+
placeholder = "Search…",
|
|
39
|
+
emptyText = "No results.",
|
|
40
|
+
disabled,
|
|
41
|
+
className,
|
|
42
|
+
}: ComboboxProps) {
|
|
43
|
+
const { contains } = useFilter({ sensitivity: "base" });
|
|
44
|
+
const { collection, filter, set } = useListCollection({
|
|
45
|
+
initialItems: items,
|
|
46
|
+
filter: contains,
|
|
47
|
+
itemToValue: (item) => item.value,
|
|
48
|
+
itemToString: (item) => item.label,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// `useListCollection` seeds `initialItems` once; reseed when the item set changes
|
|
52
|
+
// (an async loader can deliver a different list after mount). Key on the values so
|
|
53
|
+
// this fires on real changes, not on every render — the parent passes a fresh array
|
|
54
|
+
// each time, so depending on the array reference would loop.
|
|
55
|
+
const itemsKey = items.map((item) => item.value).join(" ");
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
set(items);
|
|
58
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59
|
+
}, [itemsKey]);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<ComboboxPrimitive.Root
|
|
63
|
+
data-slot="combobox"
|
|
64
|
+
collection={collection}
|
|
65
|
+
value={value ? [value] : []}
|
|
66
|
+
onValueChange={(details: ComboboxValueChangeDetails) =>
|
|
67
|
+
onValueChange?.(details.value[0] ?? "")
|
|
68
|
+
}
|
|
69
|
+
onInputValueChange={(details: ComboboxInputValueChangeDetails) => filter(details.inputValue)}
|
|
70
|
+
disabled={disabled}
|
|
71
|
+
openOnClick
|
|
72
|
+
className={cn("relative w-full", className)}
|
|
73
|
+
>
|
|
74
|
+
<ComboboxPrimitive.Control className="relative">
|
|
75
|
+
<MagnifyingGlassIcon className="pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 opacity-50" />
|
|
76
|
+
<ComboboxPrimitive.Input
|
|
77
|
+
data-slot="combobox-input"
|
|
78
|
+
placeholder={placeholder}
|
|
79
|
+
className={cn(
|
|
80
|
+
"h-9 w-full rounded-md border border-input bg-background pl-8 pr-3 text-sm shadow-sm outline-none",
|
|
81
|
+
"placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-ring",
|
|
82
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
83
|
+
)}
|
|
84
|
+
/>
|
|
85
|
+
</ComboboxPrimitive.Control>
|
|
86
|
+
<ComboboxPrimitive.Positioner className="z-50">
|
|
87
|
+
<ComboboxPrimitive.Content
|
|
88
|
+
data-slot="combobox-content"
|
|
89
|
+
className={cn(
|
|
90
|
+
"z-50 max-h-72 w-[var(--reference-width)] overflow-x-hidden overflow-y-auto rounded-xl border border-border bg-popover p-1.5 text-popover-foreground shadow-md outline-none",
|
|
91
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
92
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
93
|
+
)}
|
|
94
|
+
>
|
|
95
|
+
{collection.items.map((item) => (
|
|
96
|
+
<ComboboxPrimitive.Item
|
|
97
|
+
key={item.value}
|
|
98
|
+
item={item}
|
|
99
|
+
data-slot="combobox-item"
|
|
100
|
+
className={cn(
|
|
101
|
+
"relative flex cursor-pointer select-none items-center gap-2 rounded-lg px-3 py-2 text-sm outline-none",
|
|
102
|
+
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
|
|
103
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
<ComboboxPrimitive.ItemText>{item.label}</ComboboxPrimitive.ItemText>
|
|
107
|
+
<ComboboxPrimitive.ItemIndicator className="ml-auto">
|
|
108
|
+
<CheckIcon className="size-4" />
|
|
109
|
+
</ComboboxPrimitive.ItemIndicator>
|
|
110
|
+
</ComboboxPrimitive.Item>
|
|
111
|
+
))}
|
|
112
|
+
{collection.items.length === 0 && (
|
|
113
|
+
<div class="py-6 text-center text-sm text-muted-foreground">{emptyText}</div>
|
|
114
|
+
)}
|
|
115
|
+
</ComboboxPrimitive.Content>
|
|
116
|
+
</ComboboxPrimitive.Positioner>
|
|
117
|
+
</ComboboxPrimitive.Root>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import type { ComponentChildren } from "preact";
|
|
2
|
+
import { useState } from "preact/hooks";
|
|
3
|
+
import { EyeIcon, EyeSlashIcon, FadersIcon, LockIcon, ListChecksIcon } from "./icons.tsx";
|
|
4
|
+
import { Input } from "./input.tsx";
|
|
5
|
+
import { Switch } from "./switch.tsx";
|
|
6
|
+
|
|
7
|
+
export type ConfigFieldKind = "text" | "secret" | "number" | "boolean" | "enum";
|
|
8
|
+
|
|
9
|
+
const ICONS: Record<ConfigFieldKind, ComponentChildren> = {
|
|
10
|
+
secret: <LockIcon className="w-[22px] h-[22px]" />,
|
|
11
|
+
enum: <ListChecksIcon className="w-[22px] h-[22px]" />,
|
|
12
|
+
text: <FadersIcon className="w-[22px] h-[22px]" />,
|
|
13
|
+
number: <FadersIcon className="w-[22px] h-[22px]" />,
|
|
14
|
+
boolean: <FadersIcon className="w-[22px] h-[22px]" />,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
interface ConfigFieldProps {
|
|
18
|
+
kind: ConfigFieldKind;
|
|
19
|
+
label: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
/** Omit to hide the required/optional badge. */
|
|
23
|
+
required?: boolean;
|
|
24
|
+
/** Choices for `kind="enum"`. */
|
|
25
|
+
values?: string[];
|
|
26
|
+
/** Bounds for `kind="number"`, forwarded to the `<input>`. */
|
|
27
|
+
min?: number;
|
|
28
|
+
max?: number;
|
|
29
|
+
step?: number;
|
|
30
|
+
/** Controlled value. Pair with `onChange`. */
|
|
31
|
+
value?: string | number | boolean;
|
|
32
|
+
onChange?: (value: unknown) => void;
|
|
33
|
+
/** Uncontrolled seed (also drives the input's initial state). */
|
|
34
|
+
defaultValue?: string | number | boolean;
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* For `kind="secret"`: render the secret's display mask as the placeholder
|
|
38
|
+
* and a "leave blank to keep current value" hint instead of the regular
|
|
39
|
+
* placeholder.
|
|
40
|
+
*/
|
|
41
|
+
redacted?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* For a redacted secret: the non-reversible display mask (e.g.
|
|
44
|
+
* `pk_live_••••`) computed at wrap time. Shown as the placeholder so the
|
|
45
|
+
* operator can tell which key is set; never the input's value, so it can't
|
|
46
|
+
* be submitted.
|
|
47
|
+
*/
|
|
48
|
+
secretMask?: string;
|
|
49
|
+
id?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Forwarded to the underlying `<input>`. Useful for docs-style previews
|
|
52
|
+
* that want to suppress browser form restoration / autofill so a prefill
|
|
53
|
+
* value isn't replaced by the user's last typed value on reload.
|
|
54
|
+
*/
|
|
55
|
+
autoComplete?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ConfigField({
|
|
59
|
+
kind,
|
|
60
|
+
label,
|
|
61
|
+
description,
|
|
62
|
+
placeholder,
|
|
63
|
+
required,
|
|
64
|
+
values,
|
|
65
|
+
min,
|
|
66
|
+
max,
|
|
67
|
+
step,
|
|
68
|
+
value,
|
|
69
|
+
onChange,
|
|
70
|
+
defaultValue,
|
|
71
|
+
disabled,
|
|
72
|
+
redacted,
|
|
73
|
+
secretMask,
|
|
74
|
+
id: idProp,
|
|
75
|
+
autoComplete,
|
|
76
|
+
}: ConfigFieldProps) {
|
|
77
|
+
const id = idProp ?? `cfg-${label.replace(/[^a-z0-9]+/gi, "-").toLowerCase()}`;
|
|
78
|
+
|
|
79
|
+
const [internal, setInternal] = useState<unknown>(defaultValue);
|
|
80
|
+
// Controlled mode is determined by the caller passing `onChange`. A
|
|
81
|
+
// controlled component can legitimately have `value === undefined`
|
|
82
|
+
// (a required-no-prefill input the user hasn't filled in yet), so gating
|
|
83
|
+
// on `value !== undefined` would silently flip into uncontrolled mode and
|
|
84
|
+
// drop every keystroke into local state instead of the parent's signal.
|
|
85
|
+
const isControlled = onChange !== undefined;
|
|
86
|
+
const effective = isControlled ? value : internal;
|
|
87
|
+
const emit = (v: unknown) => {
|
|
88
|
+
if (isControlled) onChange(v);
|
|
89
|
+
else setInternal(v);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className="bg-card rounded-2xl shadow p-3.5 px-4 flex items-start gap-3">
|
|
94
|
+
<span className="text-trust">{ICONS[kind]}</span>
|
|
95
|
+
<div className="flex-1 min-w-0">
|
|
96
|
+
<div className="flex items-center gap-2">
|
|
97
|
+
<label htmlFor={id} className="text-sm font-bold text-foreground cursor-pointer">
|
|
98
|
+
{label}
|
|
99
|
+
</label>
|
|
100
|
+
{required !== undefined && (
|
|
101
|
+
<span className="text-[10px] font-bold px-[7px] py-0.5 rounded uppercase tracking-wide text-muted-foreground bg-surface-light">
|
|
102
|
+
{required ? "required" : "optional"}
|
|
103
|
+
</span>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
{description && (
|
|
107
|
+
<p className="text-xs mt-1 leading-snug text-muted-foreground">{description}</p>
|
|
108
|
+
)}
|
|
109
|
+
{kind !== "boolean" && (
|
|
110
|
+
<div className="mt-2">
|
|
111
|
+
<ConfigFieldInput
|
|
112
|
+
id={id}
|
|
113
|
+
kind={kind}
|
|
114
|
+
label={label}
|
|
115
|
+
placeholder={placeholder}
|
|
116
|
+
values={values}
|
|
117
|
+
min={min}
|
|
118
|
+
max={max}
|
|
119
|
+
step={step}
|
|
120
|
+
value={effective}
|
|
121
|
+
onChange={emit}
|
|
122
|
+
disabled={disabled === true}
|
|
123
|
+
redacted={redacted === true}
|
|
124
|
+
secretMask={secretMask}
|
|
125
|
+
autoComplete={autoComplete}
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
{redacted && (
|
|
130
|
+
<p className="text-xs text-muted-foreground leading-snug italic mt-1.5">
|
|
131
|
+
Leave blank to keep current value.
|
|
132
|
+
</p>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
{kind === "boolean" && (
|
|
136
|
+
<Switch
|
|
137
|
+
id={id}
|
|
138
|
+
checked={effective === true}
|
|
139
|
+
onCheckedChange={(d: { checked: boolean }) => emit(d.checked === true)}
|
|
140
|
+
disabled={disabled === true}
|
|
141
|
+
label={label}
|
|
142
|
+
labelClassName="sr-only"
|
|
143
|
+
/>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface ConfigFieldInputProps {
|
|
150
|
+
id: string;
|
|
151
|
+
/** A boolean renders as a Switch beside the card, not as an input. */
|
|
152
|
+
kind: Exclude<ConfigFieldKind, "boolean">;
|
|
153
|
+
/** Only for the secret reveal button's aria-label — the visible label is the
|
|
154
|
+
* `<label htmlFor>` above, and several secrets on one form would otherwise
|
|
155
|
+
* all announce as an identical "Show value". */
|
|
156
|
+
label: string;
|
|
157
|
+
placeholder?: string;
|
|
158
|
+
values?: string[];
|
|
159
|
+
min?: number;
|
|
160
|
+
max?: number;
|
|
161
|
+
step?: number;
|
|
162
|
+
value: unknown;
|
|
163
|
+
onChange: (value: unknown) => void;
|
|
164
|
+
disabled: boolean;
|
|
165
|
+
redacted: boolean;
|
|
166
|
+
secretMask?: string;
|
|
167
|
+
autoComplete?: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function ConfigFieldInput({
|
|
171
|
+
id,
|
|
172
|
+
kind,
|
|
173
|
+
label,
|
|
174
|
+
placeholder,
|
|
175
|
+
values,
|
|
176
|
+
min,
|
|
177
|
+
max,
|
|
178
|
+
step,
|
|
179
|
+
value,
|
|
180
|
+
onChange,
|
|
181
|
+
disabled,
|
|
182
|
+
redacted,
|
|
183
|
+
secretMask,
|
|
184
|
+
autoComplete,
|
|
185
|
+
}: ConfigFieldInputProps) {
|
|
186
|
+
// Reveal state for `kind="secret"`. Declared unconditionally (hooks can't
|
|
187
|
+
// sit behind the kind branches below) and per-field, so revealing one
|
|
188
|
+
// secret never unmasks its neighbours.
|
|
189
|
+
const [revealed, setRevealed] = useState(false);
|
|
190
|
+
|
|
191
|
+
if (kind === "enum") {
|
|
192
|
+
return (
|
|
193
|
+
<select
|
|
194
|
+
id={id}
|
|
195
|
+
className="h-8 w-full rounded-2xl border border-border bg-secondary px-2.5 text-sm shadow-sm outline-none focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50"
|
|
196
|
+
value={typeof value === "string" ? value : ""}
|
|
197
|
+
disabled={disabled}
|
|
198
|
+
onChange={(e) => onChange((e.target as HTMLSelectElement).value)}
|
|
199
|
+
>
|
|
200
|
+
<option value="" disabled>
|
|
201
|
+
Select…
|
|
202
|
+
</option>
|
|
203
|
+
{(values ?? []).map((v) => (
|
|
204
|
+
<option key={v} value={v}>
|
|
205
|
+
{v}
|
|
206
|
+
</option>
|
|
207
|
+
))}
|
|
208
|
+
</select>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (kind === "number") {
|
|
213
|
+
return (
|
|
214
|
+
<Input
|
|
215
|
+
id={id}
|
|
216
|
+
type="number"
|
|
217
|
+
value={typeof value === "number" ? String(value) : ((value as string) ?? "")}
|
|
218
|
+
placeholder={placeholder}
|
|
219
|
+
min={min}
|
|
220
|
+
max={max}
|
|
221
|
+
step={step}
|
|
222
|
+
disabled={disabled}
|
|
223
|
+
autoComplete={autoComplete}
|
|
224
|
+
onInput={(e) => {
|
|
225
|
+
const raw = (e.target as HTMLInputElement).value;
|
|
226
|
+
onChange(raw === "" ? undefined : Number(raw));
|
|
227
|
+
}}
|
|
228
|
+
/>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (kind === "secret") {
|
|
233
|
+
return (
|
|
234
|
+
<div className="relative">
|
|
235
|
+
<Input
|
|
236
|
+
id={id}
|
|
237
|
+
type={revealed ? "text" : "password"}
|
|
238
|
+
autoComplete={autoComplete ?? "new-password"}
|
|
239
|
+
value={typeof value === "string" ? value : ""}
|
|
240
|
+
placeholder={redacted ? secretMask : placeholder}
|
|
241
|
+
disabled={disabled}
|
|
242
|
+
// Room for the reveal button, which overlays the input's right edge.
|
|
243
|
+
className="pr-9"
|
|
244
|
+
onInput={(e) => onChange((e.target as HTMLInputElement).value)}
|
|
245
|
+
/>
|
|
246
|
+
{/* An API key typed blind is a support ticket: the operator pastes or
|
|
247
|
+
types it once, cannot proof-read it, and the app fails to
|
|
248
|
+
authenticate with no way to tell a typo from a wrong key. Masking
|
|
249
|
+
by default still matters (screen shares, over-the-shoulder), so
|
|
250
|
+
this is opt-in per field rather than a global preference.
|
|
251
|
+
`type="button"` is load-bearing — this sits inside the install and
|
|
252
|
+
edit forms, and the default `submit` would deploy the app. */}
|
|
253
|
+
<button
|
|
254
|
+
type="button"
|
|
255
|
+
onClick={() => setRevealed(!revealed)}
|
|
256
|
+
disabled={disabled}
|
|
257
|
+
aria-label={`${revealed ? "Hide" : "Show"} ${label}`}
|
|
258
|
+
aria-pressed={revealed}
|
|
259
|
+
className="absolute right-0 top-0 h-8 w-9 grid place-items-center rounded-r-2xl text-muted-foreground hover:text-foreground disabled:pointer-events-none disabled:opacity-50 cursor-pointer"
|
|
260
|
+
>
|
|
261
|
+
{revealed ? (
|
|
262
|
+
<EyeSlashIcon className="w-[18px] h-[18px]" />
|
|
263
|
+
) : (
|
|
264
|
+
<EyeIcon className="w-[18px] h-[18px]" />
|
|
265
|
+
)}
|
|
266
|
+
</button>
|
|
267
|
+
</div>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<Input
|
|
273
|
+
id={id}
|
|
274
|
+
type="text"
|
|
275
|
+
value={typeof value === "string" ? value : ""}
|
|
276
|
+
placeholder={placeholder}
|
|
277
|
+
disabled={disabled}
|
|
278
|
+
autoComplete={autoComplete}
|
|
279
|
+
onInput={(e) => onChange((e.target as HTMLInputElement).value)}
|
|
280
|
+
/>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export { ConfigField };
|