@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,124 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The 软件更新 settings page: one live snapshot of the update channel plus
|
|
4
|
+
* its two configuration seats. The phase (and progress) arrive as full
|
|
5
|
+
* `update/status` snapshots, so the view is a pure projection — check /
|
|
6
|
+
* download / restart-to-install buttons map one-to-one onto the update
|
|
7
|
+
* domain's methods, and the feed URL / auto-check seats write
|
|
8
|
+
* `settings.json` keys (hot-applied, no restart).
|
|
9
|
+
*
|
|
10
|
+
* @module @snap-rail/framework-shadcn/settings-update-page
|
|
11
|
+
*/
|
|
12
|
+
// Wire rows for the update-domain methods and the settings keys this page calls.
|
|
13
|
+
import '@snap-rail/app-boot/contract';
|
|
14
|
+
import { UPDATE_AUTO_CHECK_KEY, UPDATE_FEED_URL_KEY, updateStatusSchema, } from '@snap-rail/app-boot/contract';
|
|
15
|
+
import { rpcErrorText, subscribeFrame } from '@snap-rail/connection';
|
|
16
|
+
import { Button, Input, Label, Progress, Switch, } from "./ui/index.js";
|
|
17
|
+
import { settingsChangedSchema } from '@snap-rail/station-rpc/contract';
|
|
18
|
+
import { useEffect, useState } from 'react';
|
|
19
|
+
/** The phase's one-line copy; `error`/`unconfigured` carry their own message. */
|
|
20
|
+
function phaseHint(status) {
|
|
21
|
+
switch (status.phase) {
|
|
22
|
+
case 'unsupported': return '当前为开发模式,无法检查更新。';
|
|
23
|
+
case 'unconfigured': return status.message ?? '未配置更新源。';
|
|
24
|
+
case 'idle': return '就绪,可手动检查更新。';
|
|
25
|
+
case 'checking': return '正在检查更新…';
|
|
26
|
+
case 'available': return '发现新版本,正在后台自动下载。';
|
|
27
|
+
case 'none': return '已是最新版本。';
|
|
28
|
+
case 'downloading': return `正在下载新版本… ${Math.round(status.percent ?? 0)}%`;
|
|
29
|
+
case 'ready': return '新版本已就绪,重启后自动完成安装。';
|
|
30
|
+
case 'error': return `更新失败:${status.message ?? '未知错误'}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** The 软件更新 page. */
|
|
34
|
+
export function UpdatePage({ ctx }) {
|
|
35
|
+
const [status, setStatus] = useState(undefined);
|
|
36
|
+
const [failed, setFailed] = useState(false);
|
|
37
|
+
const [busy, setBusy] = useState(false);
|
|
38
|
+
const [actionError, setActionError] = useState(undefined);
|
|
39
|
+
const [feedUrl, setFeedUrl] = useState('');
|
|
40
|
+
const [feedError, setFeedError] = useState(undefined);
|
|
41
|
+
const [autoCheck, setAutoCheck] = useState(true);
|
|
42
|
+
const link = ctx.get('client').link;
|
|
43
|
+
// Mount reads seed the snapshot and the two seats; the frames keep every
|
|
44
|
+
// surface converged afterwards (update/status on every transition,
|
|
45
|
+
// settings/changed when a seat changes — including our own writes).
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
void link.call('update.state', {}).then(result => {
|
|
48
|
+
if (result.ok)
|
|
49
|
+
setStatus(result.value.status);
|
|
50
|
+
else
|
|
51
|
+
setFailed(true);
|
|
52
|
+
}).catch(() => setFailed(true));
|
|
53
|
+
void link.call('settings.get', { key: UPDATE_AUTO_CHECK_KEY }).then(result => {
|
|
54
|
+
if (result.ok && typeof result.value.value === 'boolean')
|
|
55
|
+
setAutoCheck(result.value.value);
|
|
56
|
+
}).catch(() => undefined);
|
|
57
|
+
void link.call('settings.get', { key: UPDATE_FEED_URL_KEY }).then(result => {
|
|
58
|
+
if (result.ok && typeof result.value.value === 'string')
|
|
59
|
+
setFeedUrl(result.value.value);
|
|
60
|
+
}).catch(() => undefined);
|
|
61
|
+
const offStatus = subscribeFrame(link, 'update/status', updateStatusSchema, next => setStatus(next));
|
|
62
|
+
const offSettings = subscribeFrame(link, 'settings/changed', settingsChangedSchema, change => {
|
|
63
|
+
if (change.key === UPDATE_FEED_URL_KEY)
|
|
64
|
+
setFeedUrl(typeof change.value === 'string' ? change.value : '');
|
|
65
|
+
if (change.key === UPDATE_AUTO_CHECK_KEY)
|
|
66
|
+
setAutoCheck(change.value !== false);
|
|
67
|
+
});
|
|
68
|
+
return () => {
|
|
69
|
+
offStatus();
|
|
70
|
+
offSettings();
|
|
71
|
+
};
|
|
72
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
73
|
+
}, [ctx]);
|
|
74
|
+
/** Run a snapshot-returning update method; failures surface next to the buttons. */
|
|
75
|
+
const run = (method) => {
|
|
76
|
+
setBusy(true);
|
|
77
|
+
setActionError(undefined);
|
|
78
|
+
void link.call(method, {}).then(result => {
|
|
79
|
+
if (result.ok)
|
|
80
|
+
setStatus(result.value.status);
|
|
81
|
+
else
|
|
82
|
+
setActionError(rpcErrorText(result.error));
|
|
83
|
+
setBusy(false);
|
|
84
|
+
}).catch(() => setBusy(false));
|
|
85
|
+
};
|
|
86
|
+
const install = () => {
|
|
87
|
+
setBusy(true);
|
|
88
|
+
setActionError(undefined);
|
|
89
|
+
void link.call('update.install', {}).then(result => {
|
|
90
|
+
if (!result.ok) {
|
|
91
|
+
setActionError(rpcErrorText(result.error));
|
|
92
|
+
setBusy(false);
|
|
93
|
+
}
|
|
94
|
+
// On success the host quits into the installer; nothing left to render.
|
|
95
|
+
}).catch(() => setBusy(false));
|
|
96
|
+
};
|
|
97
|
+
const saveFeedUrl = () => {
|
|
98
|
+
const value = feedUrl.trim();
|
|
99
|
+
if (value !== '') {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = new URL(value);
|
|
102
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
103
|
+
throw new Error('protocol');
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
setFeedError('请填写 http(s) 开头的更新源地址');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
setFeedError(undefined);
|
|
111
|
+
void link.call('settings.set', { key: UPDATE_FEED_URL_KEY, value });
|
|
112
|
+
};
|
|
113
|
+
if (failed)
|
|
114
|
+
return _jsx("div", { className: "text-sm text-muted-foreground", children: "\u66F4\u65B0\u72B6\u6001\u8BFB\u53D6\u5931\u8D25\u3002" });
|
|
115
|
+
if (status === undefined)
|
|
116
|
+
return _jsx("div", { className: "text-sm text-muted-foreground", children: "\u6B63\u5728\u8FDE\u63A5\u66F4\u65B0\u901A\u9053\u2026" });
|
|
117
|
+
const phase = status.phase;
|
|
118
|
+
const showCheck = phase === 'idle' || phase === 'none' || phase === 'error' || phase === 'available';
|
|
119
|
+
const percent = Math.min(100, Math.max(0, Math.round(status.percent ?? 0)));
|
|
120
|
+
return (_jsxs("div", { "data-region": "update-page", className: "flex max-w-xl flex-col gap-4", children: [_jsxs("div", { "data-update-status": phase, className: "flex flex-col gap-3 rounded-md border border-border px-4 py-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "\u5F53\u524D\u7248\u672C" }), _jsx("span", { className: "font-mono text-sm", children: status.currentVersion })] }), status.version !== undefined && (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "\u65B0\u7248\u672C" }), _jsx("span", { className: "font-mono text-sm", children: status.version })] })), _jsx("p", { "data-update-hint": true, className: "text-sm text-muted-foreground", children: phaseHint(status) }), phase === 'downloading' && (_jsx(Progress, { "data-update-progress": percent, "aria-label": "\u4E0B\u8F7D\u8FDB\u5EA6", value: percent })), actionError !== undefined && _jsx("p", { "data-update-error": true, className: "text-sm text-destructive", children: actionError }), _jsxs("div", { className: "flex items-center gap-2", children: [showCheck && (_jsx(Button, { "data-update-check": true, disabled: busy, onClick: () => run('update.check'), children: phase === 'error' ? '重新检查' : '检查更新' })), phase === 'available' && (_jsx(Button, { "data-update-download": true, variant: "outline", disabled: busy, onClick: () => run('update.download'), children: "\u7ACB\u5373\u4E0B\u8F7D" })), phase === 'ready' && (_jsx(Button, { "data-update-install": true, disabled: busy, onClick: install, children: "\u91CD\u542F\u5E76\u5B89\u88C5" }))] })] }), _jsxs("div", { className: "flex flex-col gap-3 rounded-md border border-border px-4 py-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-sm", children: "\u542F\u52A8\u65F6\u81EA\u52A8\u68C0\u67E5\u66F4\u65B0" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "\u53D1\u73B0\u65B0\u7248\u672C\u540E\u81EA\u52A8\u540E\u53F0\u4E0B\u8F7D\uFF1B\u5B89\u88C5\u4ECD\u9700\u624B\u52A8\u786E\u8BA4\u91CD\u542F\u3002" })] }), _jsx(Switch, { "aria-label": "\u542F\u52A8\u65F6\u81EA\u52A8\u68C0\u67E5\u66F4\u65B0", checked: autoCheck, onCheckedChange: next => {
|
|
121
|
+
setAutoCheck(next);
|
|
122
|
+
void link.call('settings.set', { key: UPDATE_AUTO_CHECK_KEY, value: next });
|
|
123
|
+
} })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "update-feed-url", children: "\u66F4\u65B0\u6E90\u5730\u5740" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { id: "update-feed-url", value: feedUrl, placeholder: "https://updates.example.com/snap-rail/", onChange: event => setFeedUrl(event.target.value) }), _jsx(Button, { variant: "outline", "data-update-save-feed": true, onClick: saveFeedUrl, children: "\u4FDD\u5B58" })] }), feedError !== undefined && _jsx("p", { className: "text-sm text-destructive", children: feedError }), _jsx("p", { className: "text-xs text-muted-foreground", children: "\u6307\u5411\u5B58\u653E\u5B89\u88C5\u5305\u4E0E latest.yml \u7684\u76EE\u5F55\uFF1B\u7559\u7A7A\u65F6\u4F7F\u7528\u5B89\u88C5\u5305\u5185\u7F6E\u914D\u7F6E\u3002\u4FDD\u5B58\u540E\u518D\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u7ACB\u5373\u751F\u6548\u3002" })] })] })] }));
|
|
124
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The UI panel seam: a small vocabulary of well-known slots that any client
|
|
3
|
+
* plugin can occupy, plus the `ctx.uiSlots` registry backing it. The
|
|
4
|
+
* vocabulary is deliberately closed for phase 1; `custom:*` ids are the
|
|
5
|
+
* reserved extension seat for community layouts.
|
|
6
|
+
*
|
|
7
|
+
* Degradation rule: every slot is optional. The layout owner decides what an
|
|
8
|
+
* empty slot renders (usually nothing); nothing throws on a missing occupant.
|
|
9
|
+
*
|
|
10
|
+
* Occupancy modes: `stack` slots render every occupant in order (menu items,
|
|
11
|
+
* actions); `single` slots render only the winner — the highest
|
|
12
|
+
* `precedence`, ties by latest registration — so a component plugin can
|
|
13
|
+
* replace a default tenant (a third-party titlebar over the suite's
|
|
14
|
+
* built-in one) and unloading it falls back to the default automatically:
|
|
15
|
+
* the default is never destroyed, only shadowed. The framework owns the mode
|
|
16
|
+
* table for its standard slots; `custom:*` slots are stack.
|
|
17
|
+
*
|
|
18
|
+
* @module @snap-rail/framework-shadcn/slots
|
|
19
|
+
*/
|
|
20
|
+
import { Context, type Plugin } from '@snap-rail/cordis';
|
|
21
|
+
import type { ReactNode } from 'react';
|
|
22
|
+
/** Stable semantic slot names shared by every layout implementation. */
|
|
23
|
+
export declare const UI_SLOT_IDS: {
|
|
24
|
+
readonly layout: "shell.layout";
|
|
25
|
+
readonly titlebar: "shell.titlebar";
|
|
26
|
+
readonly titlebarActions: "shell.titlebar.actions";
|
|
27
|
+
readonly menu: "shell.menu";
|
|
28
|
+
readonly sidebar: "shell.sidebar";
|
|
29
|
+
readonly main: "shell.main";
|
|
30
|
+
readonly view: "shell.view";
|
|
31
|
+
readonly statusbar: "shell.statusbar";
|
|
32
|
+
readonly overlay: "shell.overlay";
|
|
33
|
+
};
|
|
34
|
+
/** Runtime set used by manifest/semantic validation as well as TypeScript. */
|
|
35
|
+
export declare const STANDARD_UI_SLOT_IDS: ReadonlySet<string>;
|
|
36
|
+
/** A custom slot is owned by a publisher and a local slot name. */
|
|
37
|
+
export declare const CUSTOM_UI_SLOT_PATTERN: RegExp;
|
|
38
|
+
/** Check the public slot naming contract at runtime. */
|
|
39
|
+
export declare function isValidUiSlotId(value: string): value is SlotId;
|
|
40
|
+
/** A layout may add a namespaced extension slot without changing the core vocabulary. */
|
|
41
|
+
export type SlotId = typeof UI_SLOT_IDS[keyof typeof UI_SLOT_IDS] | `custom:${string}`;
|
|
42
|
+
/** How a slot renders its occupants: all in order, or one elected winner. */
|
|
43
|
+
export type UiSlotMode = 'stack' | 'single';
|
|
44
|
+
/**
|
|
45
|
+
* The mode table for the standard slots — the framework owns it.
|
|
46
|
+
*
|
|
47
|
+
* `titlebar` and `main` are `single`: exactly one tenant renders (a skin or
|
|
48
|
+
* replacement displaces the default and falls back on unload). `layout`
|
|
49
|
+
* stays `stack` on purpose — the settings dialog is a permanent overlay
|
|
50
|
+
* occupant beside the active layout, and the fallback shell yields by
|
|
51
|
+
* counting; every other slot collects contributions.
|
|
52
|
+
*/
|
|
53
|
+
export declare const UI_SLOT_MODES: {
|
|
54
|
+
readonly "shell.layout": "stack";
|
|
55
|
+
readonly "shell.titlebar": "single";
|
|
56
|
+
readonly "shell.titlebar.actions": "stack";
|
|
57
|
+
readonly "shell.menu": "stack";
|
|
58
|
+
readonly "shell.sidebar": "stack";
|
|
59
|
+
readonly "shell.main": "single";
|
|
60
|
+
readonly "shell.view": "stack";
|
|
61
|
+
readonly "shell.statusbar": "stack";
|
|
62
|
+
readonly "shell.overlay": "stack";
|
|
63
|
+
};
|
|
64
|
+
/** The occupancy mode of a slot; `custom:*` slots are `stack`. */
|
|
65
|
+
export declare function slotModeOf(slot: SlotId): UiSlotMode;
|
|
66
|
+
/** What one occupant contributes to a slot. */
|
|
67
|
+
export interface SlotOccupant {
|
|
68
|
+
/** Stable plugin-chosen key; later registrations with it replace earlier ones. */
|
|
69
|
+
id: string;
|
|
70
|
+
/** Rendering order within the slot; ties break by registration time. */
|
|
71
|
+
order: number;
|
|
72
|
+
/**
|
|
73
|
+
* Winner election for `single` slots: the highest value renders, and
|
|
74
|
+
* unloading it falls back to the next (a built-in default stays at 0, an
|
|
75
|
+
* override registers higher). Ignored in `stack` slots. Default 0.
|
|
76
|
+
*/
|
|
77
|
+
precedence?: number;
|
|
78
|
+
render(): ReactNode;
|
|
79
|
+
}
|
|
80
|
+
/** Registry of slot occupants exposed as `ctx.uiSlots`. */
|
|
81
|
+
export interface UiSlotsService {
|
|
82
|
+
/**
|
|
83
|
+
* Current occupants of a slot in render order. `single` slots answer with
|
|
84
|
+
* at most one occupant — the elected winner.
|
|
85
|
+
*
|
|
86
|
+
* @param slot - the slot to read.
|
|
87
|
+
*/
|
|
88
|
+
list(slot: SlotId): readonly SlotOccupant[];
|
|
89
|
+
/** Occupy a slot; disposal removes the occupant.
|
|
90
|
+
* @param slot - target slot.
|
|
91
|
+
* @param occupant - the contribution; same-id re-registration replaces.
|
|
92
|
+
* @param caller - owning context; unload drops the occupant.
|
|
93
|
+
*/
|
|
94
|
+
register(caller: Context, slot: SlotId, occupant: SlotOccupant): () => void;
|
|
95
|
+
}
|
|
96
|
+
declare module '@snap-rail/cordis' {
|
|
97
|
+
interface Context {
|
|
98
|
+
uiSlots: UiSlotsService;
|
|
99
|
+
}
|
|
100
|
+
interface Events {
|
|
101
|
+
/** A slot's occupants changed (any add, replace, or removal).
|
|
102
|
+
* @param slot - the affected slot id.
|
|
103
|
+
*/
|
|
104
|
+
'ui/slot-changed'(slot: SlotId): void;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Mounts the `ctx.uiSlots` registry.
|
|
108
|
+
*
|
|
109
|
+
* The facade binds everything to construction-time closures: traceable
|
|
110
|
+
* context proxies rebind `this` on every method access, so `this`-reached
|
|
111
|
+
* state cannot back a service here (mirrors the field seam's external core).
|
|
112
|
+
*/
|
|
113
|
+
declare const slotsPlugin: Plugin.Object<void>;
|
|
114
|
+
export default slotsPlugin;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The UI panel seam: a small vocabulary of well-known slots that any client
|
|
3
|
+
* plugin can occupy, plus the `ctx.uiSlots` registry backing it. The
|
|
4
|
+
* vocabulary is deliberately closed for phase 1; `custom:*` ids are the
|
|
5
|
+
* reserved extension seat for community layouts.
|
|
6
|
+
*
|
|
7
|
+
* Degradation rule: every slot is optional. The layout owner decides what an
|
|
8
|
+
* empty slot renders (usually nothing); nothing throws on a missing occupant.
|
|
9
|
+
*
|
|
10
|
+
* Occupancy modes: `stack` slots render every occupant in order (menu items,
|
|
11
|
+
* actions); `single` slots render only the winner — the highest
|
|
12
|
+
* `precedence`, ties by latest registration — so a component plugin can
|
|
13
|
+
* replace a default tenant (a third-party titlebar over the suite's
|
|
14
|
+
* built-in one) and unloading it falls back to the default automatically:
|
|
15
|
+
* the default is never destroyed, only shadowed. The framework owns the mode
|
|
16
|
+
* table for its standard slots; `custom:*` slots are stack.
|
|
17
|
+
*
|
|
18
|
+
* @module @snap-rail/framework-shadcn/slots
|
|
19
|
+
*/
|
|
20
|
+
import { Context } from '@snap-rail/cordis';
|
|
21
|
+
/** Stable semantic slot names shared by every layout implementation. */
|
|
22
|
+
export const UI_SLOT_IDS = {
|
|
23
|
+
layout: 'shell.layout',
|
|
24
|
+
titlebar: 'shell.titlebar',
|
|
25
|
+
titlebarActions: 'shell.titlebar.actions',
|
|
26
|
+
menu: 'shell.menu',
|
|
27
|
+
sidebar: 'shell.sidebar',
|
|
28
|
+
main: 'shell.main',
|
|
29
|
+
view: 'shell.view',
|
|
30
|
+
statusbar: 'shell.statusbar',
|
|
31
|
+
overlay: 'shell.overlay',
|
|
32
|
+
};
|
|
33
|
+
/** Runtime set used by manifest/semantic validation as well as TypeScript. */
|
|
34
|
+
export const STANDARD_UI_SLOT_IDS = new Set(Object.values(UI_SLOT_IDS));
|
|
35
|
+
/** A custom slot is owned by a publisher and a local slot name. */
|
|
36
|
+
export const CUSTOM_UI_SLOT_PATTERN = /^custom:[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*$/;
|
|
37
|
+
/** Check the public slot naming contract at runtime. */
|
|
38
|
+
export function isValidUiSlotId(value) {
|
|
39
|
+
return STANDARD_UI_SLOT_IDS.has(value) || CUSTOM_UI_SLOT_PATTERN.test(value);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The mode table for the standard slots — the framework owns it.
|
|
43
|
+
*
|
|
44
|
+
* `titlebar` and `main` are `single`: exactly one tenant renders (a skin or
|
|
45
|
+
* replacement displaces the default and falls back on unload). `layout`
|
|
46
|
+
* stays `stack` on purpose — the settings dialog is a permanent overlay
|
|
47
|
+
* occupant beside the active layout, and the fallback shell yields by
|
|
48
|
+
* counting; every other slot collects contributions.
|
|
49
|
+
*/
|
|
50
|
+
export const UI_SLOT_MODES = {
|
|
51
|
+
[UI_SLOT_IDS.layout]: 'stack',
|
|
52
|
+
[UI_SLOT_IDS.titlebar]: 'single',
|
|
53
|
+
[UI_SLOT_IDS.titlebarActions]: 'stack',
|
|
54
|
+
[UI_SLOT_IDS.menu]: 'stack',
|
|
55
|
+
[UI_SLOT_IDS.sidebar]: 'stack',
|
|
56
|
+
[UI_SLOT_IDS.main]: 'single',
|
|
57
|
+
[UI_SLOT_IDS.view]: 'stack',
|
|
58
|
+
[UI_SLOT_IDS.statusbar]: 'stack',
|
|
59
|
+
[UI_SLOT_IDS.overlay]: 'stack',
|
|
60
|
+
};
|
|
61
|
+
const STANDARD_SLOT_MODES = new Map(Object.entries(UI_SLOT_MODES));
|
|
62
|
+
/** The occupancy mode of a slot; `custom:*` slots are `stack`. */
|
|
63
|
+
export function slotModeOf(slot) {
|
|
64
|
+
return STANDARD_SLOT_MODES.get(slot) ?? 'stack';
|
|
65
|
+
}
|
|
66
|
+
/** Mounts the `ctx.uiSlots` registry.
|
|
67
|
+
*
|
|
68
|
+
* The facade binds everything to construction-time closures: traceable
|
|
69
|
+
* context proxies rebind `this` on every method access, so `this`-reached
|
|
70
|
+
* state cannot back a service here (mirrors the field seam's external core).
|
|
71
|
+
*/
|
|
72
|
+
const slotsPlugin = {
|
|
73
|
+
name: 'framework slots',
|
|
74
|
+
apply(ctx) {
|
|
75
|
+
const slots = new Map();
|
|
76
|
+
const entryOf = (slot) => {
|
|
77
|
+
let entry = slots.get(slot);
|
|
78
|
+
if (entry === undefined) {
|
|
79
|
+
entry = { occupants: new Map(), nextSeq: 0 };
|
|
80
|
+
slots.set(slot, entry);
|
|
81
|
+
}
|
|
82
|
+
return entry;
|
|
83
|
+
};
|
|
84
|
+
ctx.provide('uiSlots', {
|
|
85
|
+
list(slot) {
|
|
86
|
+
const entry = entryOf(slot);
|
|
87
|
+
const ranked = [...entry.occupants.values()]
|
|
88
|
+
.sort((a, b) => a.occupant.order - b.occupant.order || a.seq - b.seq);
|
|
89
|
+
if (slotModeOf(slot) === 'single' && ranked.length > 0) {
|
|
90
|
+
// Winner election: precedence first, latest registration breaks
|
|
91
|
+
// ties. Losers stay registered — dropping the winner falls back.
|
|
92
|
+
const winner = ranked.reduce((best, item) => {
|
|
93
|
+
const byPrecedence = (item.occupant.precedence ?? 0) - (best.occupant.precedence ?? 0);
|
|
94
|
+
return byPrecedence > 0 || (byPrecedence === 0 && item.seq > best.seq) ? item : best;
|
|
95
|
+
});
|
|
96
|
+
return [winner.occupant];
|
|
97
|
+
}
|
|
98
|
+
return ranked.map(item => item.occupant);
|
|
99
|
+
},
|
|
100
|
+
register(caller, slot, occupant) {
|
|
101
|
+
const entry = entryOf(slot);
|
|
102
|
+
entry.occupants.set(occupant.id, { occupant, seq: entry.nextSeq++ });
|
|
103
|
+
ctx.emit('ui/slot-changed', slot);
|
|
104
|
+
const remove = () => {
|
|
105
|
+
// Disposal is by occupant id: an earlier handle still removes the
|
|
106
|
+
// seat after a same-id replacement (fiber unload drops the current
|
|
107
|
+
// occupant), and repeat calls are no-ops.
|
|
108
|
+
if (entry.occupants.delete(occupant.id))
|
|
109
|
+
ctx.emit('ui/slot-changed', slot);
|
|
110
|
+
};
|
|
111
|
+
// Effects take a body producing the disposer; passing `remove`
|
|
112
|
+
// itself would run it as setup.
|
|
113
|
+
caller.effect(() => remove);
|
|
114
|
+
return remove;
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
export default slotsPlugin;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Badge: small status/label chip. `success` carries the online/live state,
|
|
3
|
+
* `warning` the attention state (e.g. partial failure, float type),
|
|
4
|
+
* `destructive` the offline/abnormal one.
|
|
5
|
+
*
|
|
6
|
+
* @module @snap-rail/framework-shadcn/ui/badge
|
|
7
|
+
*/
|
|
8
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
9
|
+
import type { ComponentProps } from 'react';
|
|
10
|
+
declare const badgeVariants: (props?: ({
|
|
11
|
+
variant?: "default" | "destructive" | "outline" | "secondary" | "success" | "warning" | null | undefined;
|
|
12
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
13
|
+
/** Props of {@link Badge}. */
|
|
14
|
+
export type BadgeProps = ComponentProps<'span'> & VariantProps<typeof badgeVariants>;
|
|
15
|
+
/** A themed status chip. */
|
|
16
|
+
export declare function Badge({ className, variant, ...props }: BadgeProps): import("react").JSX.Element;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Badge: small status/label chip. `success` carries the online/live state,
|
|
4
|
+
* `warning` the attention state (e.g. partial failure, float type),
|
|
5
|
+
* `destructive` the offline/abnormal one.
|
|
6
|
+
*
|
|
7
|
+
* @module @snap-rail/framework-shadcn/ui/badge
|
|
8
|
+
*/
|
|
9
|
+
import { cva } from 'class-variance-authority';
|
|
10
|
+
import { cn } from "./utils.js";
|
|
11
|
+
const badgeVariants = cva('inline-flex items-center gap-1 rounded-md border px-2.5 py-1 text-sm font-medium whitespace-nowrap', {
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
default: 'border-transparent bg-primary text-primary-foreground',
|
|
15
|
+
secondary: 'border-transparent bg-secondary text-secondary-foreground',
|
|
16
|
+
success: 'border-transparent bg-success/15 text-success',
|
|
17
|
+
warning: 'border-transparent bg-warning/15 text-warning',
|
|
18
|
+
destructive: 'border-transparent bg-destructive/15 text-destructive',
|
|
19
|
+
outline: 'text-muted-foreground',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: 'default',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
/** A themed status chip. */
|
|
27
|
+
export function Badge({ className, variant, ...props }) {
|
|
28
|
+
return _jsx("span", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
29
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button: cva variants over a native button. Ghost/outline cover chrome
|
|
3
|
+
* surfaces (titlebar, panel toggles); `icon` size squares the padding for
|
|
4
|
+
* glyph-only controls.
|
|
5
|
+
*
|
|
6
|
+
* @module @snap-rail/framework-shadcn/ui/button
|
|
7
|
+
*/
|
|
8
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
9
|
+
import type { ComponentProps } from 'react';
|
|
10
|
+
declare const buttonVariants: (props?: ({
|
|
11
|
+
variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
|
|
12
|
+
size?: "default" | "xl" | "lg" | "sm" | "icon" | null | undefined;
|
|
13
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
14
|
+
/** Props of {@link Button}. */
|
|
15
|
+
export type ButtonProps = ComponentProps<'button'> & VariantProps<typeof buttonVariants> & {
|
|
16
|
+
asChild?: boolean;
|
|
17
|
+
};
|
|
18
|
+
/** A themed button; `asChild` renders the child element instead. */
|
|
19
|
+
export declare function Button({ className, variant, size, asChild, ...props }: ButtonProps): import("react").JSX.Element;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Button: cva variants over a native button. Ghost/outline cover chrome
|
|
4
|
+
* surfaces (titlebar, panel toggles); `icon` size squares the padding for
|
|
5
|
+
* glyph-only controls.
|
|
6
|
+
*
|
|
7
|
+
* @module @snap-rail/framework-shadcn/ui/button
|
|
8
|
+
*/
|
|
9
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
10
|
+
import { cva } from 'class-variance-authority';
|
|
11
|
+
import { cn } from "./utils.js";
|
|
12
|
+
const buttonVariants = cva('inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0', {
|
|
13
|
+
variants: {
|
|
14
|
+
variant: {
|
|
15
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
16
|
+
destructive: 'bg-destructive/90 text-destructive-foreground hover:bg-destructive',
|
|
17
|
+
outline: 'border border-border bg-transparent hover:bg-accent hover:text-accent-foreground',
|
|
18
|
+
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
19
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
20
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
21
|
+
},
|
|
22
|
+
size: {
|
|
23
|
+
xl: 'h-16 px-8 text-lg',
|
|
24
|
+
lg: 'h-14 px-6 text-lg',
|
|
25
|
+
default: 'h-12 px-5 py-1.5',
|
|
26
|
+
sm: 'h-10 px-4 text-sm',
|
|
27
|
+
icon: 'h-12 w-12',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
variant: 'default',
|
|
32
|
+
size: 'default',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
/** A themed button; `asChild` renders the child element instead. */
|
|
36
|
+
export function Button({ className, variant, size, asChild = false, ...props }) {
|
|
37
|
+
const Component = asChild ? Slot : 'button';
|
|
38
|
+
return _jsx(Component, { className: cn(buttonVariants({ variant, size }), className), ...props });
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card: the panel container family (card, header, title, content).
|
|
3
|
+
*
|
|
4
|
+
* @module @snap-rail/framework-shadcn/ui/card
|
|
5
|
+
*/
|
|
6
|
+
import type { ComponentProps } from 'react';
|
|
7
|
+
/** The container surface. */
|
|
8
|
+
export declare function Card({ className, ...props }: ComponentProps<'div'>): import("react").JSX.Element;
|
|
9
|
+
/** Top padding/heading zone of a card. */
|
|
10
|
+
export declare function CardHeader({ className, ...props }: ComponentProps<'div'>): import("react").JSX.Element;
|
|
11
|
+
/** Card heading line. */
|
|
12
|
+
export declare function CardTitle({ className, ...props }: ComponentProps<'h3'>): import("react").JSX.Element;
|
|
13
|
+
/** Body zone of a card. */
|
|
14
|
+
export declare function CardContent({ className, ...props }: ComponentProps<'div'>): import("react").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "./utils.js";
|
|
3
|
+
/** The container surface. */
|
|
4
|
+
export function Card({ className, ...props }) {
|
|
5
|
+
return _jsx("div", { className: cn('rounded-lg border border-border bg-card text-card-foreground', className), ...props });
|
|
6
|
+
}
|
|
7
|
+
/** Top padding/heading zone of a card. */
|
|
8
|
+
export function CardHeader({ className, ...props }) {
|
|
9
|
+
return _jsx("div", { className: cn('flex flex-col gap-1 p-5', className), ...props });
|
|
10
|
+
}
|
|
11
|
+
/** Card heading line. */
|
|
12
|
+
export function CardTitle({ className, ...props }) {
|
|
13
|
+
return _jsx("h3", { className: cn('text-sm font-medium text-muted-foreground', className), ...props });
|
|
14
|
+
}
|
|
15
|
+
/** Body zone of a card. */
|
|
16
|
+
export function CardContent({ className, ...props }) {
|
|
17
|
+
return _jsx("div", { className: cn('p-5 pt-0', className), ...props });
|
|
18
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkbox: themed check entry over Radix Checkbox; the indicator is an
|
|
3
|
+
* inline stroke so the UI seam needs no icon dependency.
|
|
4
|
+
*
|
|
5
|
+
* @module @snap-rail/framework-shadcn/ui/checkbox
|
|
6
|
+
*/
|
|
7
|
+
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
|
8
|
+
import type { ComponentProps } from 'react';
|
|
9
|
+
/** A themed checkbox. */
|
|
10
|
+
export declare function Checkbox({ className, ...props }: ComponentProps<typeof CheckboxPrimitive.Root>): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Checkbox: themed check entry over Radix Checkbox; the indicator is an
|
|
4
|
+
* inline stroke so the UI seam needs no icon dependency.
|
|
5
|
+
*
|
|
6
|
+
* @module @snap-rail/framework-shadcn/ui/checkbox
|
|
7
|
+
*/
|
|
8
|
+
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
|
9
|
+
import { cn } from "./utils.js";
|
|
10
|
+
/** A themed checkbox. */
|
|
11
|
+
export function Checkbox({ className, ...props }) {
|
|
12
|
+
return (_jsx(CheckboxPrimitive.Root, { className: cn('h-6 w-6 shrink-0 rounded-sm border border-input bg-transparent', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', 'data-[state=checked]:border-primary data-[state=checked]:bg-primary', 'disabled:cursor-not-allowed disabled:opacity-50', className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { className: "flex items-center justify-center text-primary-foreground", children: _jsx("svg", { viewBox: "0 0 12 12", className: "h-4 w-4", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M2 6.5 4.8 9 10 3.5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }) }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collapsible: the shadcn collapsible over Radix Collapsible — an
|
|
3
|
+
* independently controlled show/hide section. The root carries the card
|
|
4
|
+
* surface. A section may carry several triggers (title zone, chevron zone);
|
|
5
|
+
* each renders the shared {@link CollapsibleChevron} where it wants the
|
|
6
|
+
* rotating arrow. Icons are inline strokes so the UI seam needs no icon
|
|
7
|
+
* dependency.
|
|
8
|
+
*
|
|
9
|
+
* @module @snap-rail/framework-shadcn/ui/collapsible
|
|
10
|
+
*/
|
|
11
|
+
import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
|
|
12
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
13
|
+
/** Controlled/uncontrolled root; renders the bordered card surface. */
|
|
14
|
+
export declare function Collapsible({ className, ...props }: ComponentProps<typeof CollapsiblePrimitive.Root>): import("react").JSX.Element;
|
|
15
|
+
/** The header button. The `group` class lets a nested chevron sense the
|
|
16
|
+
* open state (see {@link CollapsibleChevron}). */
|
|
17
|
+
export declare function CollapsibleTrigger({ className, ...props }: ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>): import("react").JSX.Element;
|
|
18
|
+
/** The rotating disclosure arrow; renders inside a trigger and turns with
|
|
19
|
+
* that trigger's open state. */
|
|
20
|
+
export declare function CollapsibleChevron({ className }: {
|
|
21
|
+
className?: string;
|
|
22
|
+
}): ReactNode;
|
|
23
|
+
/** The body; mounted only while open. */
|
|
24
|
+
export declare function CollapsibleContent({ className, ...props }: ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>): import("react").JSX.Element;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Collapsible: the shadcn collapsible over Radix Collapsible — an
|
|
4
|
+
* independently controlled show/hide section. The root carries the card
|
|
5
|
+
* surface. A section may carry several triggers (title zone, chevron zone);
|
|
6
|
+
* each renders the shared {@link CollapsibleChevron} where it wants the
|
|
7
|
+
* rotating arrow. Icons are inline strokes so the UI seam needs no icon
|
|
8
|
+
* dependency.
|
|
9
|
+
*
|
|
10
|
+
* @module @snap-rail/framework-shadcn/ui/collapsible
|
|
11
|
+
*/
|
|
12
|
+
import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
|
|
13
|
+
import { cn } from "./utils.js";
|
|
14
|
+
/** Controlled/uncontrolled root; renders the bordered card surface. */
|
|
15
|
+
export function Collapsible({ className, ...props }) {
|
|
16
|
+
return (_jsx(CollapsiblePrimitive.Root, { className: cn('rounded-md border border-border bg-card', className), ...props }));
|
|
17
|
+
}
|
|
18
|
+
/** The header button. The `group` class lets a nested chevron sense the
|
|
19
|
+
* open state (see {@link CollapsibleChevron}). */
|
|
20
|
+
export function CollapsibleTrigger({ className, ...props }) {
|
|
21
|
+
return (_jsx(CollapsiblePrimitive.CollapsibleTrigger, { className: cn('group flex w-full items-center gap-2 text-sm font-medium', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', className), ...props }));
|
|
22
|
+
}
|
|
23
|
+
/** The rotating disclosure arrow; renders inside a trigger and turns with
|
|
24
|
+
* that trigger's open state. */
|
|
25
|
+
export function CollapsibleChevron({ className }) {
|
|
26
|
+
return (_jsx("svg", { viewBox: "0 0 12 12", className: cn('h-4 w-4 shrink-0 opacity-60 transition-transform duration-200 group-data-[state=open]:rotate-180', className), fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "m2.5 4.5 3.5 3.5 3.5-3.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
27
|
+
}
|
|
28
|
+
/** The body; mounted only while open. */
|
|
29
|
+
export function CollapsibleContent({ className, ...props }) {
|
|
30
|
+
return (_jsx(CollapsiblePrimitive.CollapsibleContent, { className: cn('overflow-hidden', className), ...props }));
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialog: modal overlay + panel over Radix Dialog. Chrome surfaces use it
|
|
3
|
+
* for confirmations (window close, technician id entry); content pages use
|
|
4
|
+
* it for small forms. No built-in close affordance — callers render their
|
|
5
|
+
* own buttons in the footer.
|
|
6
|
+
*
|
|
7
|
+
* @module @snap-rail/framework-shadcn/ui/dialog
|
|
8
|
+
*/
|
|
9
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
10
|
+
import type { ComponentProps } from 'react';
|
|
11
|
+
/** Controlled/uncontrolled root; re-exported so callers need one import. */
|
|
12
|
+
export declare const Dialog: import("react").FC<DialogPrimitive.DialogProps>;
|
|
13
|
+
/** The overlay + centered panel; mounts through a portal into body. */
|
|
14
|
+
export declare function DialogContent({ className, children, ...props }: ComponentProps<typeof DialogPrimitive.Content>): import("react").JSX.Element;
|
|
15
|
+
/** Title bar block inside a dialog. */
|
|
16
|
+
export declare function DialogHeader({ className, ...props }: ComponentProps<'div'>): import("react").JSX.Element;
|
|
17
|
+
/** The dialog's accessible title. */
|
|
18
|
+
export declare const DialogTitle: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogTitleProps & import("react").RefAttributes<HTMLHeadingElement>>;
|
|
19
|
+
/** Optional supporting line under the title. */
|
|
20
|
+
export declare const DialogDescription: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogDescriptionProps & import("react").RefAttributes<HTMLParagraphElement>>;
|
|
21
|
+
/** Action row (buttons) at the bottom; defaults to right-aligned. */
|
|
22
|
+
export declare function DialogFooter({ className, ...props }: ComponentProps<'div'>): import("react").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Dialog: modal overlay + panel over Radix Dialog. Chrome surfaces use it
|
|
4
|
+
* for confirmations (window close, technician id entry); content pages use
|
|
5
|
+
* it for small forms. No built-in close affordance — callers render their
|
|
6
|
+
* own buttons in the footer.
|
|
7
|
+
*
|
|
8
|
+
* @module @snap-rail/framework-shadcn/ui/dialog
|
|
9
|
+
*/
|
|
10
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
11
|
+
import { cn } from "./utils.js";
|
|
12
|
+
/** Controlled/uncontrolled root; re-exported so callers need one import. */
|
|
13
|
+
export const Dialog = DialogPrimitive.Root;
|
|
14
|
+
/** The overlay + centered panel; mounts through a portal into body. */
|
|
15
|
+
export function DialogContent({ className, children, ...props }) {
|
|
16
|
+
return (_jsxs(DialogPrimitive.Portal, { children: [_jsx(DialogPrimitive.Overlay, { className: "overlay-in fixed inset-0 z-50 bg-background/80 backdrop-blur-[1px]" }), _jsx(DialogPrimitive.Content, { className: cn('dialog-in fixed left-1/2 top-1/2 z-50 w-[calc(100%-2.5rem)] max-w-md -translate-x-1/2 -translate-y-1/2', 'rounded-lg border border-border bg-card text-card-foreground shadow-xl', 'p-5 focus:outline-none', className), ...props, children: children })] }));
|
|
17
|
+
}
|
|
18
|
+
/** Title bar block inside a dialog. */
|
|
19
|
+
export function DialogHeader({ className, ...props }) {
|
|
20
|
+
return _jsx("div", { className: cn('mb-4 space-y-1', className), ...props });
|
|
21
|
+
}
|
|
22
|
+
/** The dialog's accessible title. */
|
|
23
|
+
export const DialogTitle = DialogPrimitive.Title;
|
|
24
|
+
/** Optional supporting line under the title. */
|
|
25
|
+
export const DialogDescription = DialogPrimitive.Description;
|
|
26
|
+
/** Action row (buttons) at the bottom; defaults to right-aligned. */
|
|
27
|
+
export function DialogFooter({ className, ...props }) {
|
|
28
|
+
return _jsx("div", { className: cn('mt-5 flex justify-end gap-3', className), ...props });
|
|
29
|
+
}
|