@snap-rail/framework-shadcn 0.0.0-stage → 0.2.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/README.md +20 -2
- package/docs/framework-authoring.md +59 -0
- package/lib/fallback.js +307 -0
- package/lib/index.js +3036 -0
- package/lib/runtime.js +2827 -0
- package/lib/semantic.js +96 -0
- package/lib/session.js +48 -0
- package/lib/settings-pages.js +68 -0
- package/lib/slots.js +95 -0
- package/lib/types/Shell.d.ts +13 -0
- package/lib/types/Shell.js +17 -0
- package/lib/types/client.d.ts +41 -0
- package/lib/types/client.js +39 -0
- package/lib/types/fallback.d.ts +15 -0
- package/lib/types/fallback.js +52 -0
- package/lib/types/index.d.ts +17 -0
- package/lib/types/index.js +17 -0
- package/lib/types/runtime.d.ts +53 -0
- package/lib/types/runtime.js +75 -0
- package/lib/types/semantic.d.ts +55 -0
- package/lib/types/semantic.js +63 -0
- package/lib/types/session.d.ts +43 -0
- package/lib/types/session.js +76 -0
- package/lib/types/settings-dialog.d.ts +17 -0
- package/lib/types/settings-dialog.js +138 -0
- package/lib/types/settings-pages.d.ts +68 -0
- package/lib/types/settings-pages.js +85 -0
- package/lib/types/settings-plugins-page.d.ts +29 -0
- package/lib/types/settings-plugins-page.js +466 -0
- package/lib/types/settings-update-page.d.ts +17 -0
- package/lib/types/settings-update-page.js +124 -0
- package/lib/types/slots.d.ts +114 -0
- package/lib/types/slots.js +119 -0
- package/lib/types/ui/badge.d.ts +17 -0
- package/lib/types/ui/badge.js +29 -0
- package/lib/types/ui/button.d.ts +20 -0
- package/lib/types/ui/button.js +39 -0
- package/lib/types/ui/card.d.ts +14 -0
- package/lib/types/ui/card.js +18 -0
- package/lib/types/ui/checkbox.d.ts +10 -0
- package/lib/types/ui/checkbox.js +13 -0
- package/lib/types/ui/collapsible.d.ts +24 -0
- package/lib/types/ui/collapsible.js +31 -0
- package/lib/types/ui/dialog.d.ts +22 -0
- package/lib/types/ui/dialog.js +29 -0
- package/lib/types/ui/drag-scroll.d.ts +13 -0
- package/lib/types/ui/drag-scroll.js +85 -0
- package/lib/types/ui/hooks.d.ts +16 -0
- package/lib/types/ui/hooks.js +26 -0
- package/lib/types/ui/icons.d.ts +21 -0
- package/lib/types/ui/icons.js +18 -0
- package/lib/types/ui/index.d.ts +32 -0
- package/lib/types/ui/index.js +32 -0
- package/lib/types/ui/input.d.ts +8 -0
- package/lib/types/ui/input.js +6 -0
- package/lib/types/ui/label.d.ts +9 -0
- package/lib/types/ui/label.js +12 -0
- package/lib/types/ui/led.d.ts +13 -0
- package/lib/types/ui/led.js +11 -0
- package/lib/types/ui/number-input.d.ts +35 -0
- package/lib/types/ui/number-input.js +48 -0
- package/lib/types/ui/progress.d.ts +10 -0
- package/lib/types/ui/progress.js +14 -0
- package/lib/types/ui/radio-group.d.ts +12 -0
- package/lib/types/ui/radio-group.js +17 -0
- package/lib/types/ui/schema-form.d.ts +51 -0
- package/lib/types/ui/schema-form.js +124 -0
- package/lib/types/ui/scroll-area.d.ts +10 -0
- package/lib/types/ui/scroll-area.js +13 -0
- package/lib/types/ui/switch.d.ts +10 -0
- package/lib/types/ui/switch.js +13 -0
- package/lib/types/ui/table.d.ts +20 -0
- package/lib/types/ui/table.js +28 -0
- package/lib/types/ui/tabs.d.ts +16 -0
- package/lib/types/ui/tabs.js +23 -0
- package/lib/types/ui/textarea.d.ts +10 -0
- package/lib/types/ui/textarea.js +6 -0
- package/lib/types/ui/theme.d.ts +30 -0
- package/lib/types/ui/theme.js +33 -0
- package/lib/types/ui/tooltip.d.ts +23 -0
- package/lib/types/ui/tooltip.js +15 -0
- package/lib/types/ui/touch-select.d.ts +29 -0
- package/lib/types/ui/touch-select.js +37 -0
- package/lib/types/ui/utils.d.ts +13 -0
- package/lib/types/ui/utils.js +16 -0
- package/lib/types/variables.d.ts +168 -0
- package/lib/types/variables.js +282 -0
- package/lib/types/workflows.d.ts +98 -0
- package/lib/types/workflows.js +95 -0
- package/lib/ui/index.js +1101 -0
- package/lib/ui/theme.css +271 -0
- package/lib/variables.js +260 -0
- package/lib/workflows.js +85 -0
- package/package.json +138 -3
- package/src/ui/theme.css +271 -0
- package/templates/ui-extension/README.md +17 -0
- package/templates/ui-view/README.md +17 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DragScroll: an overflow-auto container that also pans when held and
|
|
3
|
+
* dragged with the mouse (both axes). Touch and pen keep Chromium's native
|
|
4
|
+
* pan, so only mouse pointers are intercepted. A 6px threshold separates a
|
|
5
|
+
* pan from a press; the click that follows a pan is swallowed so dragged
|
|
6
|
+
* rows never fire. DragScrolls nest (a table inside a scrolled page); only
|
|
7
|
+
* the innermost container under the pressed target takes the gesture.
|
|
8
|
+
*
|
|
9
|
+
* @module @snap-rail/framework-shadcn/ui/drag-scroll
|
|
10
|
+
*/
|
|
11
|
+
import { type ComponentProps, type ReactNode } from 'react';
|
|
12
|
+
/** An auto-scrolling div that pans under a held mouse drag. */
|
|
13
|
+
export declare function DragScroll({ className, children, ...props }: ComponentProps<'div'>): ReactNode;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* DragScroll: an overflow-auto container that also pans when held and
|
|
4
|
+
* dragged with the mouse (both axes). Touch and pen keep Chromium's native
|
|
5
|
+
* pan, so only mouse pointers are intercepted. A 6px threshold separates a
|
|
6
|
+
* pan from a press; the click that follows a pan is swallowed so dragged
|
|
7
|
+
* rows never fire. DragScrolls nest (a table inside a scrolled page); only
|
|
8
|
+
* the innermost container under the pressed target takes the gesture.
|
|
9
|
+
*
|
|
10
|
+
* @module @snap-rail/framework-shadcn/ui/drag-scroll
|
|
11
|
+
*/
|
|
12
|
+
import { useRef } from 'react';
|
|
13
|
+
import { cn } from "./utils.js";
|
|
14
|
+
/** Movement (px) before a held press turns into a pan. */
|
|
15
|
+
const PAN_THRESHOLD = 6;
|
|
16
|
+
/** An auto-scrolling div that pans under a held mouse drag. */
|
|
17
|
+
export function DragScroll({ className, children, ...props }) {
|
|
18
|
+
const container = useRef(null);
|
|
19
|
+
const pan = useRef(null);
|
|
20
|
+
const swallowClick = useRef(false);
|
|
21
|
+
const onPointerDown = (event) => {
|
|
22
|
+
if (event.pointerType !== 'mouse' || event.button !== 0)
|
|
23
|
+
return;
|
|
24
|
+
// DragScrolls nest (a table inside a scrolled page). If both armed on
|
|
25
|
+
// one press, the ancestor's setPointerCapture would steal the pointer
|
|
26
|
+
// from the innermost container mid-gesture: its pan freezes and its
|
|
27
|
+
// state leaks past the release into a hover-follow. Only the innermost
|
|
28
|
+
// container under the pressed target arms.
|
|
29
|
+
if (container.current === null)
|
|
30
|
+
return;
|
|
31
|
+
const innermost = event.target instanceof Element ? event.target.closest('[data-drag-scroll]') : null;
|
|
32
|
+
if (innermost !== null && innermost !== container.current)
|
|
33
|
+
return;
|
|
34
|
+
pan.current = { id: event.pointerId, x: event.clientX, y: event.clientY, moved: false };
|
|
35
|
+
};
|
|
36
|
+
const onPointerMove = (event) => {
|
|
37
|
+
const state = pan.current;
|
|
38
|
+
if (state === null || state.id !== event.pointerId)
|
|
39
|
+
return;
|
|
40
|
+
// A buttonless move means the press ended without our pointerup — the
|
|
41
|
+
// capture was stolen. Drop the stale pan instead of following hover.
|
|
42
|
+
if (event.buttons === 0) {
|
|
43
|
+
pan.current = null;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const el = container.current;
|
|
47
|
+
if (el === null)
|
|
48
|
+
return;
|
|
49
|
+
const dx = state.x - event.clientX;
|
|
50
|
+
const dy = state.y - event.clientY;
|
|
51
|
+
if (!state.moved) {
|
|
52
|
+
if (Math.hypot(dx, dy) < PAN_THRESHOLD)
|
|
53
|
+
return;
|
|
54
|
+
state.moved = true;
|
|
55
|
+
el.setPointerCapture(event.pointerId);
|
|
56
|
+
el.dataset.panning = 'true';
|
|
57
|
+
}
|
|
58
|
+
el.scrollLeft += dx;
|
|
59
|
+
el.scrollTop += dy;
|
|
60
|
+
state.x = event.clientX;
|
|
61
|
+
state.y = event.clientY;
|
|
62
|
+
};
|
|
63
|
+
const endPan = (event) => {
|
|
64
|
+
const state = pan.current;
|
|
65
|
+
if (state === null || state.id !== event.pointerId)
|
|
66
|
+
return;
|
|
67
|
+
pan.current = null;
|
|
68
|
+
const el = container.current;
|
|
69
|
+
if (el === null)
|
|
70
|
+
return;
|
|
71
|
+
if (state.moved) {
|
|
72
|
+
if (el.hasPointerCapture(event.pointerId))
|
|
73
|
+
el.releasePointerCapture(event.pointerId);
|
|
74
|
+
delete el.dataset.panning;
|
|
75
|
+
swallowClick.current = true;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
return (_jsx("div", { ref: container, "data-drag-scroll": "", className: cn('overflow-auto', className), onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: endPan, onPointerCancel: endPan, onClickCapture: event => {
|
|
79
|
+
if (!swallowClick.current)
|
|
80
|
+
return;
|
|
81
|
+
swallowClick.current = false;
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
event.stopPropagation();
|
|
84
|
+
}, ...props, children: children }));
|
|
85
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared React recipes over the client runtime's event fabric.
|
|
3
|
+
*
|
|
4
|
+
* @module @snap-rail/framework-shadcn/ui/hooks
|
|
5
|
+
*/
|
|
6
|
+
import type { Context, Events } from '@snap-rail/cordis';
|
|
7
|
+
/**
|
|
8
|
+
* Re-render the component whenever any of the named ctx events fires —
|
|
9
|
+
* the standard "surface follows the seam" tick. The cordis import is
|
|
10
|
+
* type-only: this module stays a pure React utility at runtime.
|
|
11
|
+
*
|
|
12
|
+
* @param ctx - the plugin context whose events drive the refresh.
|
|
13
|
+
* @param events - event names to listen on (identity is not part of the
|
|
14
|
+
* effect key; a freshly built array literal is fine).
|
|
15
|
+
*/
|
|
16
|
+
export declare function useRefresh(ctx: Context, events: ReadonlyArray<keyof Events>): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared React recipes over the client runtime's event fabric.
|
|
3
|
+
*
|
|
4
|
+
* @module @snap-rail/framework-shadcn/ui/hooks
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect, useState } from 'react';
|
|
7
|
+
/**
|
|
8
|
+
* Re-render the component whenever any of the named ctx events fires —
|
|
9
|
+
* the standard "surface follows the seam" tick. The cordis import is
|
|
10
|
+
* type-only: this module stays a pure React utility at runtime.
|
|
11
|
+
*
|
|
12
|
+
* @param ctx - the plugin context whose events drive the refresh.
|
|
13
|
+
* @param events - event names to listen on (identity is not part of the
|
|
14
|
+
* effect key; a freshly built array literal is fine).
|
|
15
|
+
*/
|
|
16
|
+
export function useRefresh(ctx, events) {
|
|
17
|
+
const [, setTick] = useState(0);
|
|
18
|
+
const key = events.join('\n');
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const bump = () => setTick(value => value + 1);
|
|
21
|
+
const names = (key === '' ? [] : key.split('\n'));
|
|
22
|
+
const detachers = names.map(event => ctx.on(event, bump));
|
|
23
|
+
return () => { for (const detach of detachers)
|
|
24
|
+
detach(); };
|
|
25
|
+
}, [ctx, key]);
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon primitives: lucide-style inline stroke glyphs the Button and page
|
|
3
|
+
* headers embed. Icons are inline strokes (no icon-package dependency, no
|
|
4
|
+
* fill dependencies on currentColor semantics) — same convention as
|
|
5
|
+
* {@link CollapsibleChevron}.
|
|
6
|
+
*
|
|
7
|
+
* @module @snap-rail/framework-shadcn/ui/icons
|
|
8
|
+
*/
|
|
9
|
+
import type { ReactNode } from 'react';
|
|
10
|
+
/** A plus glyph — the "add / install something" affordance. */
|
|
11
|
+
export declare function IconPlus({ className }: {
|
|
12
|
+
className?: string;
|
|
13
|
+
}): ReactNode;
|
|
14
|
+
/** Chevrons pointing together (down-up) — the "collapse all" affordance. */
|
|
15
|
+
export declare function IconChevronsDownUp({ className }: {
|
|
16
|
+
className?: string;
|
|
17
|
+
}): ReactNode;
|
|
18
|
+
/** Chevrons pointing apart (up-down) — the "expand all" affordance. */
|
|
19
|
+
export declare function IconChevronsUpDown({ className }: {
|
|
20
|
+
className?: string;
|
|
21
|
+
}): ReactNode;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "./utils.js";
|
|
3
|
+
/** The shared stroke skeleton every icon renders. */
|
|
4
|
+
function icon(path, className) {
|
|
5
|
+
return (_jsx("svg", { viewBox: "0 0 24 24", className: cn('h-4 w-4 shrink-0', className), fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: path }));
|
|
6
|
+
}
|
|
7
|
+
/** A plus glyph — the "add / install something" affordance. */
|
|
8
|
+
export function IconPlus({ className }) {
|
|
9
|
+
return icon(_jsxs(_Fragment, { children: [_jsx("path", { d: "M5 12h14" }), _jsx("path", { d: "M12 5v14" })] }), className);
|
|
10
|
+
}
|
|
11
|
+
/** Chevrons pointing together (down-up) — the "collapse all" affordance. */
|
|
12
|
+
export function IconChevronsDownUp({ className }) {
|
|
13
|
+
return icon(_jsxs(_Fragment, { children: [_jsx("path", { d: "m7 20 5-5 5 5" }), _jsx("path", { d: "m7 4 5 5 5-5" })] }), className);
|
|
14
|
+
}
|
|
15
|
+
/** Chevrons pointing apart (up-down) — the "expand all" affordance. */
|
|
16
|
+
export function IconChevronsUpDown({ className }) {
|
|
17
|
+
return icon(_jsxs(_Fragment, { children: [_jsx("path", { d: "m7 15 5 5 5-5" }), _jsx("path", { d: "m7 9 5-5 5 5" })] }), className);
|
|
18
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The UI primitive seam: theme tokens (theme.css) plus the shadcn-style
|
|
3
|
+
* component set every UI plugin shares. Consumers import components by
|
|
4
|
+
* package name; the runtime imports `@snap-rail/framework-shadcn/theme.css` once.
|
|
5
|
+
*
|
|
6
|
+
* @module @snap-rail/framework-shadcn/ui
|
|
7
|
+
*/
|
|
8
|
+
export { cn } from './utils.ts';
|
|
9
|
+
export { useRefresh } from './hooks.tsx';
|
|
10
|
+
export { Button, type ButtonProps } from './button.tsx';
|
|
11
|
+
export { Card, CardHeader, CardTitle, CardContent } from './card.tsx';
|
|
12
|
+
export { Badge, type BadgeProps } from './badge.tsx';
|
|
13
|
+
export { Switch } from './switch.tsx';
|
|
14
|
+
export { Tooltip, TooltipRoot, type TooltipProps, type TooltipContentProps } from './tooltip.tsx';
|
|
15
|
+
export { ScrollArea } from './scroll-area.tsx';
|
|
16
|
+
export { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from './dialog.tsx';
|
|
17
|
+
export { Input } from './input.tsx';
|
|
18
|
+
export { Textarea } from './textarea.tsx';
|
|
19
|
+
export { Label } from './label.tsx';
|
|
20
|
+
export { Checkbox } from './checkbox.tsx';
|
|
21
|
+
export { Progress } from './progress.tsx';
|
|
22
|
+
export { RadioGroup, RadioGroupItem } from './radio-group.tsx';
|
|
23
|
+
export { TouchSelect, type TouchSelectOption } from './touch-select.tsx';
|
|
24
|
+
export { NumberInput, NumberPad } from './number-input.tsx';
|
|
25
|
+
export { SchemaForm, schemaDefaults, planSchemaFields, compactSchemaValue, type SchemaFormValue, } from './schema-form.tsx';
|
|
26
|
+
export { DragScroll } from './drag-scroll.tsx';
|
|
27
|
+
export { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from './table.tsx';
|
|
28
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs.tsx';
|
|
29
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleChevron, CollapsibleContent } from './collapsible.tsx';
|
|
30
|
+
export { IconPlus, IconChevronsDownUp, IconChevronsUpDown } from './icons.tsx';
|
|
31
|
+
export { Led, type LedTone } from './led.tsx';
|
|
32
|
+
export { THEME_SETTINGS_KEY, DEFAULT_THEME, applyTheme, resolveThemeMode, themeSettingsSchema, type ThemeSettings, } from './theme.ts';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The UI primitive seam: theme tokens (theme.css) plus the shadcn-style
|
|
3
|
+
* component set every UI plugin shares. Consumers import components by
|
|
4
|
+
* package name; the runtime imports `@snap-rail/framework-shadcn/theme.css` once.
|
|
5
|
+
*
|
|
6
|
+
* @module @snap-rail/framework-shadcn/ui
|
|
7
|
+
*/
|
|
8
|
+
export { cn } from "./utils.js";
|
|
9
|
+
export { useRefresh } from "./hooks.js";
|
|
10
|
+
export { Button } from "./button.js";
|
|
11
|
+
export { Card, CardHeader, CardTitle, CardContent } from "./card.js";
|
|
12
|
+
export { Badge } from "./badge.js";
|
|
13
|
+
export { Switch } from "./switch.js";
|
|
14
|
+
export { Tooltip, TooltipRoot } from "./tooltip.js";
|
|
15
|
+
export { ScrollArea } from "./scroll-area.js";
|
|
16
|
+
export { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "./dialog.js";
|
|
17
|
+
export { Input } from "./input.js";
|
|
18
|
+
export { Textarea } from "./textarea.js";
|
|
19
|
+
export { Label } from "./label.js";
|
|
20
|
+
export { Checkbox } from "./checkbox.js";
|
|
21
|
+
export { Progress } from "./progress.js";
|
|
22
|
+
export { RadioGroup, RadioGroupItem } from "./radio-group.js";
|
|
23
|
+
export { TouchSelect } from "./touch-select.js";
|
|
24
|
+
export { NumberInput, NumberPad } from "./number-input.js";
|
|
25
|
+
export { SchemaForm, schemaDefaults, planSchemaFields, compactSchemaValue, } from "./schema-form.js";
|
|
26
|
+
export { DragScroll } from "./drag-scroll.js";
|
|
27
|
+
export { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "./table.js";
|
|
28
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from "./tabs.js";
|
|
29
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleChevron, CollapsibleContent } from "./collapsible.js";
|
|
30
|
+
export { IconPlus, IconChevronsDownUp, IconChevronsUpDown } from "./icons.js";
|
|
31
|
+
export { Led } from "./led.js";
|
|
32
|
+
export { THEME_SETTINGS_KEY, DEFAULT_THEME, applyTheme, resolveThemeMode, themeSettingsSchema, } from "./theme.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input: themed single-line text entry for ids, numbers, and short values.
|
|
3
|
+
*
|
|
4
|
+
* @module @snap-rail/framework-shadcn/ui/input
|
|
5
|
+
*/
|
|
6
|
+
import type { ComponentProps } from 'react';
|
|
7
|
+
/** A themed text input. */
|
|
8
|
+
export declare function Input({ className, type, ...props }: ComponentProps<'input'>): import("react").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "./utils.js";
|
|
3
|
+
/** A themed text input. */
|
|
4
|
+
export function Input({ className, type = 'text', ...props }) {
|
|
5
|
+
return (_jsx("input", { type: type, className: cn('flex h-12 w-full rounded-md border border-input bg-transparent px-4 py-2 text-base', 'placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50', className), ...props }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Label: form field caption over Radix Label (click focuses the control).
|
|
3
|
+
*
|
|
4
|
+
* @module @snap-rail/framework-shadcn/ui/label
|
|
5
|
+
*/
|
|
6
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
7
|
+
import type { ComponentProps } from 'react';
|
|
8
|
+
/** A themed field label. */
|
|
9
|
+
export declare function Label({ className, ...props }: ComponentProps<typeof LabelPrimitive.Root>): import("react").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Label: form field caption over Radix Label (click focuses the control).
|
|
4
|
+
*
|
|
5
|
+
* @module @snap-rail/framework-shadcn/ui/label
|
|
6
|
+
*/
|
|
7
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
8
|
+
import { cn } from "./utils.js";
|
|
9
|
+
/** A themed field label. */
|
|
10
|
+
export function Label({ className, ...props }) {
|
|
11
|
+
return (_jsx(LabelPrimitive.Root, { className: cn('text-sm font-medium text-muted-foreground select-none', className), ...props }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Led: the breathing status dot connection/group health renders as. A plain
|
|
3
|
+
* span (not a Radix part) — `data-tone` is the test hook.
|
|
4
|
+
*
|
|
5
|
+
* @module @snap-rail/framework-shadcn/ui/led
|
|
6
|
+
*/
|
|
7
|
+
import type { ComponentProps } from 'react';
|
|
8
|
+
/** Health tones: green = all good, yellow = partial, red = down. */
|
|
9
|
+
export type LedTone = 'green' | 'yellow' | 'red';
|
|
10
|
+
/** One pulsing dot; pass `aria-label` when it is the only status surface. */
|
|
11
|
+
export declare function Led({ tone, className, ...props }: ComponentProps<'span'> & {
|
|
12
|
+
tone: LedTone;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "./utils.js";
|
|
3
|
+
const TONE_CLASS = {
|
|
4
|
+
green: 'bg-success',
|
|
5
|
+
yellow: 'bg-warning',
|
|
6
|
+
red: 'bg-destructive',
|
|
7
|
+
};
|
|
8
|
+
/** One pulsing dot; pass `aria-label` when it is the only status surface. */
|
|
9
|
+
export function Led({ tone, className, ...props }) {
|
|
10
|
+
return (_jsx("span", { "data-tone": tone, className: cn('inline-block h-3 w-3 shrink-0 animate-pulse rounded-full', TONE_CLASS[tone], className), ...props }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NumberInput / NumberPad: numeric entry for the touch terminal. The field
|
|
3
|
+
* is a button styled as an input (no system soft keyboard ever pops); a tap
|
|
4
|
+
* opens the NumberPad modal — a 3×4 keypad plus 清除/确定. NumberPad also
|
|
5
|
+
* mounts inline where the keypad should stay visible (the login card).
|
|
6
|
+
*
|
|
7
|
+
* @module @snap-rail/framework-shadcn/ui/number-input
|
|
8
|
+
*/
|
|
9
|
+
import { type ReactNode } from 'react';
|
|
10
|
+
/** An inline 3×4 keypad (digits, optional decimal point, backspace) with a
|
|
11
|
+
* clear/confirm action row; every key is a full-size Button. */
|
|
12
|
+
export declare function NumberPad(props: {
|
|
13
|
+
/** The current digit string (controlled). */
|
|
14
|
+
value: string;
|
|
15
|
+
onChange: (value: string) => void;
|
|
16
|
+
/** Fires on the confirm key; inline mounts use it as their primary action. */
|
|
17
|
+
onConfirm?: () => void;
|
|
18
|
+
/** Allow one decimal point (weights, scale factors). */
|
|
19
|
+
allowDecimal?: boolean;
|
|
20
|
+
/** Confirm key text (defaults to 确定). */
|
|
21
|
+
confirmLabel?: string;
|
|
22
|
+
className?: string;
|
|
23
|
+
}): ReactNode;
|
|
24
|
+
/** A numeric field: an input-styled button that opens the NumberPad modal;
|
|
25
|
+
* `onChange` commits on 确定 only — a cancelled edit changes nothing. */
|
|
26
|
+
export declare function NumberInput(props: {
|
|
27
|
+
/** Field name; doubles as the trigger's aria-label and the modal title. */
|
|
28
|
+
label: string;
|
|
29
|
+
value: string;
|
|
30
|
+
onChange: (value: string) => void;
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
allowDecimal?: boolean;
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
className?: string;
|
|
35
|
+
}): ReactNode;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* NumberInput / NumberPad: numeric entry for the touch terminal. The field
|
|
4
|
+
* is a button styled as an input (no system soft keyboard ever pops); a tap
|
|
5
|
+
* opens the NumberPad modal — a 3×4 keypad plus 清除/确定. NumberPad also
|
|
6
|
+
* mounts inline where the keypad should stay visible (the login card).
|
|
7
|
+
*
|
|
8
|
+
* @module @snap-rail/framework-shadcn/ui/number-input
|
|
9
|
+
*/
|
|
10
|
+
import { useEffect, useState } from 'react';
|
|
11
|
+
import { Button } from "./button.js";
|
|
12
|
+
import { Dialog, DialogContent, DialogTitle } from "./dialog.js";
|
|
13
|
+
import { cn } from "./utils.js";
|
|
14
|
+
/** The keypad's backspace glyph; inline so the seam carries no icon dep. */
|
|
15
|
+
function BackspaceIcon() {
|
|
16
|
+
return (_jsxs("svg", { viewBox: "0 0 24 24", className: "h-6 w-6", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: "M9 5h11a1.5 1.5 0 0 1 1.5 1.5v11A1.5 1.5 0 0 1 20 19H9l-6.5-7L9 5Z", stroke: "currentColor", strokeWidth: "1.6", strokeLinejoin: "round" }), _jsx("path", { d: "m12.5 9.5 5 5m0-5-5 5", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })] }));
|
|
17
|
+
}
|
|
18
|
+
/** An inline 3×4 keypad (digits, optional decimal point, backspace) with a
|
|
19
|
+
* clear/confirm action row; every key is a full-size Button. */
|
|
20
|
+
export function NumberPad(props) {
|
|
21
|
+
const { value, onChange, onConfirm, allowDecimal = false, confirmLabel = '确定', className } = props;
|
|
22
|
+
const press = (key) => {
|
|
23
|
+
if (key === '.' && (value.includes('.') || value === ''))
|
|
24
|
+
return;
|
|
25
|
+
onChange(value + key);
|
|
26
|
+
};
|
|
27
|
+
const backspace = () => { onChange(value.slice(0, -1)); };
|
|
28
|
+
const clear = () => { onChange(''); };
|
|
29
|
+
return (_jsxs("div", { className: cn('grid grid-cols-3 gap-2', className), children: [['1', '2', '3', '4', '5', '6', '7', '8', '9'].map(digit => (_jsx(Button, { type: "button", variant: "secondary", "data-key": digit, className: "h-14 font-mono text-xl", onClick: () => { press(digit); }, children: digit }, digit))), allowDecimal
|
|
30
|
+
? (_jsx(Button, { type: "button", variant: "secondary", "data-key": "dot", className: "h-14 font-mono text-xl", onClick: () => { press('.'); }, children: "\u00B7" }))
|
|
31
|
+
: _jsx("div", { "aria-hidden": "true" }), _jsx(Button, { type: "button", variant: "secondary", "data-key": "0", className: "h-14 font-mono text-xl", onClick: () => { press('0'); }, children: "0" }), _jsx(Button, { type: "button", variant: "secondary", "aria-label": "\u9000\u683C", "data-key": "back", className: "h-14", onClick: backspace, children: _jsx(BackspaceIcon, {}) }), _jsx(Button, { type: "button", variant: "outline", "data-key": "clear", className: "h-14 text-base", onClick: clear, children: "\u6E05\u9664" }), _jsx(Button, { type: "button", "data-key": "confirm", className: "col-span-2 h-14 text-base", onClick: () => { onConfirm?.(); }, children: confirmLabel })] }));
|
|
32
|
+
}
|
|
33
|
+
/** A numeric field: an input-styled button that opens the NumberPad modal;
|
|
34
|
+
* `onChange` commits on 确定 only — a cancelled edit changes nothing. */
|
|
35
|
+
export function NumberInput(props) {
|
|
36
|
+
const { label, value, onChange, placeholder = '', allowDecimal = false, disabled = false, className } = props;
|
|
37
|
+
const [open, setOpen] = useState(false);
|
|
38
|
+
const [draft, setDraft] = useState(value);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (open)
|
|
41
|
+
setDraft(value);
|
|
42
|
+
}, [open, value]);
|
|
43
|
+
const confirm = () => {
|
|
44
|
+
onChange(draft);
|
|
45
|
+
setOpen(false);
|
|
46
|
+
};
|
|
47
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", disabled: disabled, "aria-label": label, "data-number-input": label, className: cn('flex h-12 w-full items-center rounded-md border border-input bg-transparent px-4 text-left', 'font-mono text-base tabular-nums', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', 'disabled:cursor-not-allowed disabled:opacity-50', className), onClick: () => { setOpen(true); }, children: value === '' ? _jsx("span", { className: "text-muted-foreground", children: placeholder }) : value }), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { "aria-describedby": undefined, className: "max-w-sm p-0", children: [_jsx("div", { className: "border-b border-border px-5 py-4", children: _jsx(DialogTitle, { className: "text-base font-medium", children: label }) }), _jsxs("div", { className: "px-5 pb-5 pt-4", children: [_jsx("div", { className: "mb-4 min-h-14 rounded-md border border-border bg-muted px-4 py-3 text-right font-mono text-2xl tabular-nums", "data-number-display": true, children: draft === '' ? _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) : draft }), _jsx(NumberPad, { value: draft, onChange: setDraft, onConfirm: confirm, allowDecimal: allowDecimal })] })] }) })] }));
|
|
48
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress: the Radix progress bar with snap-rail chrome — determinate
|
|
3
|
+
* download/scan progress for long-running host work.
|
|
4
|
+
*
|
|
5
|
+
* @module @snap-rail/framework-shadcn/ui/progress
|
|
6
|
+
*/
|
|
7
|
+
import * as ProgressPrimitive from '@radix-ui/react-progress';
|
|
8
|
+
import type { ComponentProps } from 'react';
|
|
9
|
+
/** A themed linear progress bar (`value` 0–100, `aria-valuenow` included). */
|
|
10
|
+
export declare function Progress({ className, value, ...props }: ComponentProps<typeof ProgressPrimitive.Root>): import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Progress: the Radix progress bar with snap-rail chrome — determinate
|
|
4
|
+
* download/scan progress for long-running host work.
|
|
5
|
+
*
|
|
6
|
+
* @module @snap-rail/framework-shadcn/ui/progress
|
|
7
|
+
*/
|
|
8
|
+
import * as ProgressPrimitive from '@radix-ui/react-progress';
|
|
9
|
+
import { cn } from "./utils.js";
|
|
10
|
+
/** A themed linear progress bar (`value` 0–100, `aria-valuenow` included). */
|
|
11
|
+
export function Progress({ className, value, ...props }) {
|
|
12
|
+
const percent = typeof value === 'number' ? Math.min(100, Math.max(0, value)) : 0;
|
|
13
|
+
return (_jsx(ProgressPrimitive.Root, { className: cn('relative h-2 w-full overflow-hidden rounded-full bg-secondary', className), ...props, children: _jsx(ProgressPrimitive.Indicator, { className: "h-full w-full rounded-full bg-primary transition-transform", style: { transform: `translateX(-${100 - percent}%)` } }) }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RadioGroup: exclusive choice over Radix RadioGroup (是/否 switches, model
|
|
3
|
+
* picks); the indicator is a plain dot so the UI seam needs no icon dep.
|
|
4
|
+
*
|
|
5
|
+
* @module @snap-rail/framework-shadcn/ui/radio-group
|
|
6
|
+
*/
|
|
7
|
+
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
8
|
+
import type { ComponentProps } from 'react';
|
|
9
|
+
/** The exclusive group root. */
|
|
10
|
+
export declare function RadioGroup({ className, ...props }: ComponentProps<typeof RadioGroupPrimitive.Root>): import("react").JSX.Element;
|
|
11
|
+
/** One themed choice. */
|
|
12
|
+
export declare function RadioGroupItem({ className, ...props }: ComponentProps<typeof RadioGroupPrimitive.Item>): import("react").JSX.Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* RadioGroup: exclusive choice over Radix RadioGroup (是/否 switches, model
|
|
4
|
+
* picks); the indicator is a plain dot so the UI seam needs no icon dep.
|
|
5
|
+
*
|
|
6
|
+
* @module @snap-rail/framework-shadcn/ui/radio-group
|
|
7
|
+
*/
|
|
8
|
+
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
9
|
+
import { cn } from "./utils.js";
|
|
10
|
+
/** The exclusive group root. */
|
|
11
|
+
export function RadioGroup({ className, ...props }) {
|
|
12
|
+
return _jsx(RadioGroupPrimitive.Root, { className: cn('grid gap-3', className), ...props });
|
|
13
|
+
}
|
|
14
|
+
/** One themed choice. */
|
|
15
|
+
export function RadioGroupItem({ className, ...props }) {
|
|
16
|
+
return (_jsx(RadioGroupPrimitive.Item, { className: cn('aspect-square h-6 w-6 shrink-0 rounded-full border border-input bg-transparent', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', 'data-[state=checked]:border-primary disabled:cursor-not-allowed disabled:opacity-50', className), ...props, children: _jsx(RadioGroupPrimitive.Indicator, { className: "flex items-center justify-center", children: _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-primary" }) }) }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SchemaForm: the settings vocabulary's generic form engine. It renders one
|
|
3
|
+
* labeled control per property of a JSON Schema (the input-form projection
|
|
4
|
+
* drivers register with the field base — zod's `toJSONSchema`), on the touch
|
|
5
|
+
* baseline: enums and literal unions become TouchSelect rows (committing the
|
|
6
|
+
* literal's own type — a number const rides back as a number), numbers open
|
|
7
|
+
* the NumberPad, booleans ride a Switch. Property `title`s (zod
|
|
8
|
+
* `.meta({ title })`) label the fields; the host's zod schemas remain the
|
|
9
|
+
* only validator — this form shapes input, it does not judge it.
|
|
10
|
+
*
|
|
11
|
+
* The vocabulary is meant to grow (discovery pickers, repeatable groups);
|
|
12
|
+
* what a schema expresses beyond today's subset renders as a plain text
|
|
13
|
+
* field and still round-trips through the host's validation.
|
|
14
|
+
*
|
|
15
|
+
* @module @snap-rail/framework-shadcn/ui/schema-form
|
|
16
|
+
*/
|
|
17
|
+
import { type ReactNode } from 'react';
|
|
18
|
+
import { type TouchSelectOption } from './touch-select.tsx';
|
|
19
|
+
/** One form value per schema property: schema-native types, absent = unset. */
|
|
20
|
+
export type SchemaFormValue = Record<string, string | number | boolean | undefined>;
|
|
21
|
+
/** The schema subset one field renders from. */
|
|
22
|
+
interface FieldPlan {
|
|
23
|
+
name: string;
|
|
24
|
+
label: string;
|
|
25
|
+
required: boolean;
|
|
26
|
+
kind: 'enum' | 'string' | 'integer' | 'number' | 'boolean' | 'text';
|
|
27
|
+
options?: TouchSelectOption[];
|
|
28
|
+
/** option value → the schema-native value to commit — number literals stay
|
|
29
|
+
* numbers (a union of `z.literal(1)`s must round-trip as 1, never "1"). */
|
|
30
|
+
literalOf?: Record<string, string | number | boolean>;
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Plan the renderable fields of an object schema (property order kept). */
|
|
34
|
+
export declare function planSchemaFields(schema: Record<string, unknown>, hidden?: readonly string[]): FieldPlan[];
|
|
35
|
+
/** Initial values from an object schema's `default`s (create dialogs). */
|
|
36
|
+
export declare function schemaDefaults(schema: Record<string, unknown>): SchemaFormValue;
|
|
37
|
+
/** Strip keys whose value is `undefined` so unset optionals ride clean. */
|
|
38
|
+
export declare function compactSchemaValue(value: SchemaFormValue): Record<string, string | number | boolean>;
|
|
39
|
+
/** A controlled, schema-driven form body (labels + controls, no buttons). */
|
|
40
|
+
export declare function SchemaForm(props: {
|
|
41
|
+
/** The object schema driving the fields (input form, zod-projected). */
|
|
42
|
+
schema: Record<string, unknown>;
|
|
43
|
+
/** Current values keyed by property name; absent = unset. */
|
|
44
|
+
value: SchemaFormValue;
|
|
45
|
+
onChange: (value: SchemaFormValue) => void;
|
|
46
|
+
/** Property names the caller owns and renders itself (e.g. `type`). */
|
|
47
|
+
hidden?: readonly string[];
|
|
48
|
+
disabled?: boolean;
|
|
49
|
+
className?: string;
|
|
50
|
+
}): ReactNode;
|
|
51
|
+
export {};
|