view-contracts 0.1.0 → 0.3.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/cli-contract.yaml +1 -1
- package/dist/view-contracts.bundle.mjs +161 -153
- package/dist/view-contracts.bundle.mjs.map +4 -4
- package/docs/cli-reference.md +1 -1
- package/package.json +14 -4
- package/renderers/compose/templates/Screen.kt.hbs +15 -0
- package/renderers/compose/templates/VcTheme.kt.hbs +19 -0
- package/renderers/react/README.md +19 -3
- package/renderers/react/defaults/theme.yaml +41 -0
- package/renderers/react/defaults/tokens.yaml +59 -0
- package/renderers/react/element-config.meta.json +22 -0
- package/renderers/react/elementMap.ts +55 -0
- package/renderers/react/elements.yaml +86 -0
- package/renderers/react/index.ts +2 -0
- package/renderers/react/lowering/lowerToJsx.ts +433 -0
- package/renderers/react/paths.ts +10 -0
- package/renderers/react/renderComponents.ts +65 -0
- package/renderers/react/renderRuntime.ts +157 -0
- package/renderers/react/routes.yaml +3 -2
- package/{src/renderer → renderers}/react/runtime/actionRuntime.ts +3 -3
- package/renderers/react/runtime/dispatch.ts +21 -0
- package/renderers/react/runtime/index.ts +5 -0
- package/renderers/react/runtime/style.ts +101 -0
- package/renderers/react/runtime/theme.css +83 -0
- package/renderers/react/runtime/types.ts +63 -0
- package/renderers/react/templates/components-index.ts.hbs +5 -0
- package/renderers/react/templates/dispatch.ts.hbs +22 -0
- package/renderers/react/templates/handlers.ts.hbs +4 -0
- package/renderers/react/templates/runtime-types.ts.hbs +10 -0
- package/renderers/react/templates/style-helpers.ts.hbs +2 -0
- package/renderers/react/templates/theme.css.hbs +2 -0
- package/renderers/swiftui/templates/Screen.swift.hbs +21 -0
- package/renderers/swiftui/templates/VcTheme.swift.hbs +21 -0
- package/spec/ui-dsl.schema.json +24 -16
- package/src/generated/schema/allowed-components.ts +1 -1
- package/src/generated/schema/{tokens.ts → dsl-enums.ts} +1 -1
- package/src/generated/schema/dsl-properties.ts +3 -2
- package/src/generated/schema/index.ts +2 -2
- package/src/generated/schema/lowering-style-properties.ts +28 -0
- package/src/generated/schema/react-renderer-element-config.ts +25 -0
- package/src/generated/schema/schema-document.ts +2 -2
- package/src/renderer/react/runtime/components.tsx +0 -216
- package/src/renderer/react/runtime/index.ts +0 -4
- package/src/renderer/react/runtime/shared.tsx +0 -53
- package/src/renderer/react/runtime/style.ts +0 -69
- package/src/renderer/react/runtime/theme.css +0 -68
- package/src/renderer/react/runtime/types.ts +0 -46
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { dispatchAction } from '
|
|
1
|
+
import type { DispatchableAction } from './types.js';
|
|
2
|
+
import { dispatchAction } from './dispatch.js';
|
|
3
3
|
|
|
4
4
|
const globalHandlers: Record<string, (params?: unknown) => void | Promise<void>> = {};
|
|
5
5
|
|
|
@@ -15,7 +15,7 @@ export function clearPreviewActions(): void {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export async function dispatchPreviewAction(action?:
|
|
18
|
+
export async function dispatchPreviewAction(action?: DispatchableAction): Promise<void> {
|
|
19
19
|
if (!action) return;
|
|
20
20
|
const handler = globalHandlers[action.name];
|
|
21
21
|
if (handler) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DispatchableAction } from './types.js';
|
|
2
|
+
|
|
3
|
+
type ActionHandler = (action: DispatchableAction) => void | Promise<void>;
|
|
4
|
+
|
|
5
|
+
let handler: ActionHandler | undefined;
|
|
6
|
+
|
|
7
|
+
export function registerActionHandler(nextHandler: ActionHandler): void {
|
|
8
|
+
handler = nextHandler;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function dispatchAction(action: DispatchableAction): Promise<void> {
|
|
12
|
+
if (handler) {
|
|
13
|
+
await handler(action);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (action.name === 'navigate' && typeof action.params?.to === 'string') {
|
|
17
|
+
window.location.href = action.params.to;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
window.dispatchEvent(new CustomEvent('view-contracts:action', { detail: action }));
|
|
21
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Preview/toolchain React runtime primitives — not copied to generated/. */
|
|
2
|
+
export * from './types.js';
|
|
3
|
+
export { commonStyle, isHidden, toneClass, mapAlign, mapJustify } from './style.js';
|
|
4
|
+
export { registerPreviewActions, clearPreviewActions, dispatchPreviewAction } from './actionRuntime.js';
|
|
5
|
+
export { registerActionHandler, dispatchAction } from './dispatch.js';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import type { Align, ContentAlign, FontWeight, Justify } from '../../../src/generated/schema/dsl-enums.js';
|
|
3
|
+
import type { CommonProps } from './types.js';
|
|
4
|
+
|
|
5
|
+
export function mapAlign(value?: Align): string | undefined {
|
|
6
|
+
if (!value) return undefined;
|
|
7
|
+
if (value === 'start') return 'flex-start';
|
|
8
|
+
if (value === 'end') return 'flex-end';
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function mapJustify(value?: Justify | string): string | undefined {
|
|
13
|
+
if (!value) return undefined;
|
|
14
|
+
if (value === 'start') return 'flex-start';
|
|
15
|
+
if (value === 'end') return 'flex-end';
|
|
16
|
+
if (value === 'between' || value === 'spaceBetween') return 'space-between';
|
|
17
|
+
if (value === 'around' || value === 'spaceAround') return 'space-around';
|
|
18
|
+
if (value === 'spaceEvenly') return 'space-evenly';
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function len(value: number): string {
|
|
23
|
+
return `${value}px`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const CONTENT_ALIGN: Record<ContentAlign, string> = {
|
|
27
|
+
topStart: 'start start',
|
|
28
|
+
top: 'center start',
|
|
29
|
+
topEnd: 'end start',
|
|
30
|
+
start: 'start center',
|
|
31
|
+
center: 'center center',
|
|
32
|
+
end: 'end center',
|
|
33
|
+
bottomStart: 'start end',
|
|
34
|
+
bottom: 'center end',
|
|
35
|
+
bottomEnd: 'end end',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function fontSizeStyle(size: number | string | undefined): string | undefined {
|
|
39
|
+
if (size === undefined) return undefined;
|
|
40
|
+
return typeof size === 'number' ? len(size) : size;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function commonStyle(props: CommonProps): CSSProperties {
|
|
44
|
+
const style: CSSProperties = {};
|
|
45
|
+
if (typeof props.width === 'number') style.width = len(props.width);
|
|
46
|
+
if (typeof props.minWidth === 'number') style.minWidth = len(props.minWidth);
|
|
47
|
+
if (typeof props.maxWidth === 'number') style.maxWidth = len(props.maxWidth);
|
|
48
|
+
if (typeof props.height === 'number') style.height = len(props.height);
|
|
49
|
+
if (typeof props.minHeight === 'number') style.minHeight = len(props.minHeight);
|
|
50
|
+
if (typeof props.maxHeight === 'number') style.maxHeight = len(props.maxHeight);
|
|
51
|
+
if (props.aspectRatio !== undefined) style.aspectRatio = String(props.aspectRatio);
|
|
52
|
+
if (typeof props.weight === 'number') style.flexGrow = props.weight;
|
|
53
|
+
if (typeof props.padding === 'number') style.padding = len(props.padding);
|
|
54
|
+
if (typeof (props as { margin?: number }).margin === 'number') style.margin = len((props as { margin: number }).margin);
|
|
55
|
+
if (typeof props.gap === 'number') style.gap = len(props.gap);
|
|
56
|
+
if (props.textAlign) style.textAlign = props.textAlign === 'start' ? 'left' : props.textAlign === 'end' ? 'right' : 'center';
|
|
57
|
+
if (props.contentAlign) style.placeItems = CONTENT_ALIGN[props.contentAlign];
|
|
58
|
+
if ((props as { overflow?: string }).overflow) style.overflow = (props as { overflow: string }).overflow;
|
|
59
|
+
if (props.selfAlign) style.alignSelf = props.selfAlign === 'start' ? 'flex-start' : props.selfAlign === 'end' ? 'flex-end' : props.selfAlign;
|
|
60
|
+
const fontSize = fontSizeStyle(props.size);
|
|
61
|
+
if (fontSize) style.fontSize = fontSize;
|
|
62
|
+
const fontWeight = typeof props.weight === 'string' ? props.weight : props.fontWeight;
|
|
63
|
+
if (fontWeight) style.fontWeight = fontWeight as FontWeight;
|
|
64
|
+
return style;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isHidden(props: CommonProps): boolean {
|
|
68
|
+
return props.visible === false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Sample/extension helper — tone is not in ui-dsl.schema.json */
|
|
72
|
+
export function toneClass(tone: string | undefined): string {
|
|
73
|
+
return tone ? `ui-tone-${tone}` : 'ui-tone-default';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function componentToKebab(name: string): string {
|
|
77
|
+
return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const DEFAULT_VARIANT_ALIASES = new Set(['default', 'muted']);
|
|
81
|
+
|
|
82
|
+
/** Resolve theme variant name from variant or legacy tone prop. */
|
|
83
|
+
export function resolveExtensionVariant(variant?: string, tone?: string): string {
|
|
84
|
+
const raw = variant ?? tone;
|
|
85
|
+
if (!raw || DEFAULT_VARIANT_ALIASES.has(raw)) return 'default';
|
|
86
|
+
return raw;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Build vc-* base + --variant modifier for themed extension components. */
|
|
90
|
+
export function extensionClassName(
|
|
91
|
+
component: string,
|
|
92
|
+
options?: { variant?: string; tone?: string; extra?: string | false },
|
|
93
|
+
): string {
|
|
94
|
+
const base = `vc-${componentToKebab(component)}`;
|
|
95
|
+
const variant = resolveExtensionVariant(options?.variant, options?.tone);
|
|
96
|
+
const parts = [base, `${base}--${variant}`];
|
|
97
|
+
if (options?.extra) parts.push(options.extra);
|
|
98
|
+
return parts.filter(Boolean).join(' ');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type { ContentAlign };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--ui-bg: #f6f8fb;
|
|
3
|
+
--ui-surface: #ffffff;
|
|
4
|
+
--ui-surface-muted: #f8fafc;
|
|
5
|
+
--ui-text: #111827;
|
|
6
|
+
--ui-text-muted: #6b7280;
|
|
7
|
+
--ui-primary: #2563eb;
|
|
8
|
+
--ui-primary-weak: #dbeafe;
|
|
9
|
+
--ui-success: #047857;
|
|
10
|
+
--ui-success-weak: #d1fae5;
|
|
11
|
+
--ui-warning: #b45309;
|
|
12
|
+
--ui-warning-weak: #fef3c7;
|
|
13
|
+
--ui-danger: #dc2626;
|
|
14
|
+
--ui-danger-weak: #fee2e2;
|
|
15
|
+
--ui-border: #e5e7eb;
|
|
16
|
+
--ui-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
|
|
17
|
+
--ui-font: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
* { box-sizing: border-box; }
|
|
21
|
+
body { margin: 0; background: var(--ui-bg); color: var(--ui-text); font-family: var(--ui-font); }
|
|
22
|
+
button, input, textarea, select { font: inherit; }
|
|
23
|
+
|
|
24
|
+
.vc-page { min-height: 100vh; }
|
|
25
|
+
.vc-box { display: block; }
|
|
26
|
+
.vc-column { display: flex; flex-direction: column; }
|
|
27
|
+
.vc-stack { display: flex; flex-direction: column; }
|
|
28
|
+
.vc-row { display: flex; flex-direction: row; }
|
|
29
|
+
.vc-wrap { flex-wrap: wrap; }
|
|
30
|
+
.vc-spacer { min-width: 0; min-height: 0; }
|
|
31
|
+
.vc-divider { border: 0; border-top: 1px solid var(--ui-border); width: 100%; }
|
|
32
|
+
|
|
33
|
+
.vc-heading { margin: 0; line-height: 1.2; letter-spacing: -0.02em; }
|
|
34
|
+
.vc-heading-1 { font-size: 32px; }
|
|
35
|
+
.vc-heading-2 { font-size: 22px; }
|
|
36
|
+
.vc-heading-3 { font-size: 18px; }
|
|
37
|
+
.vc-heading-4 { font-size: 15px; }
|
|
38
|
+
.vc-text { margin: 0; line-height: 1.6; }
|
|
39
|
+
.vc-link { color: var(--ui-primary); text-decoration: none; }
|
|
40
|
+
.vc-link:hover { text-decoration: underline; }
|
|
41
|
+
|
|
42
|
+
.ui-tone-default { color: var(--ui-text); }
|
|
43
|
+
.ui-tone-muted { color: var(--ui-text-muted); }
|
|
44
|
+
.ui-tone-primary { color: var(--ui-primary); }
|
|
45
|
+
.ui-tone-success { color: var(--ui-success); }
|
|
46
|
+
.ui-tone-warning { color: var(--ui-warning); }
|
|
47
|
+
.ui-tone-danger { color: var(--ui-danger); }
|
|
48
|
+
|
|
49
|
+
.vc-button { border: 0; border-radius: 10px; padding: 10px 14px; font-weight: 700; cursor: pointer; }
|
|
50
|
+
|
|
51
|
+
.vc-form { display: flex; flex-direction: column; }
|
|
52
|
+
.vc-field { display: flex; flex-direction: column; gap: 6px; }
|
|
53
|
+
.vc-field-label { font-size: 13px; font-weight: 700; color: var(--ui-text); }
|
|
54
|
+
.vc-field-help { font-size: 12px; color: var(--ui-text-muted); }
|
|
55
|
+
.vc-field-error { font-size: 12px; color: var(--ui-danger); }
|
|
56
|
+
.vc-text-input, .vc-select, .vc-text-area { width: 100%; border: 1px solid var(--ui-border); border-radius: 10px; background: #fff; color: var(--ui-text); outline: none; }
|
|
57
|
+
.vc-text-input:focus, .vc-select:focus, .vc-text-area:focus { border-color: var(--ui-primary); box-shadow: 0 0 0 3px var(--ui-primary-weak); }
|
|
58
|
+
.vc-field-sm { min-height: 34px; padding: 6px 10px; }
|
|
59
|
+
.vc-field-md, .vc-text-input, .vc-select { min-height: 42px; padding: 9px 12px; }
|
|
60
|
+
.vc-field-lg { min-height: 50px; padding: 12px 14px; }
|
|
61
|
+
.vc-text-area { padding: 10px 12px; resize: vertical; }
|
|
62
|
+
.vc-checkbox { display: inline-flex; align-items: center; gap: 8px; }
|
|
63
|
+
.vc-list { margin: 0; padding-left: 20px; }
|
|
64
|
+
.vc-list-item { margin: 0; padding: 2px 0; }
|
|
65
|
+
|
|
66
|
+
/* Extension components — structural layout; variant colors from theme.yaml via emitVariantCss */
|
|
67
|
+
.vc-container { width: 100%; max-width: 1180px; margin: 0 auto; }
|
|
68
|
+
.vc-grid { display: grid; }
|
|
69
|
+
.vc-card { border: 1px solid var(--ui-border); }
|
|
70
|
+
.vc-badge { display: inline-flex; align-items: center; justify-content: center; padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; }
|
|
71
|
+
.vc-alert { display: flex; gap: 12px; border: 1px solid var(--ui-border); border-radius: 12px; padding: 14px; }
|
|
72
|
+
.vc-alert--success { border-color: #a7f3d0; }
|
|
73
|
+
.vc-alert--warning { border-color: #fde68a; }
|
|
74
|
+
.vc-alert--danger { border-color: #fecaca; }
|
|
75
|
+
.vc-table-wrap { overflow-x: auto; border: 1px solid var(--ui-border); border-radius: 14px; background: #fff; }
|
|
76
|
+
.vc-table { width: 100%; border-collapse: collapse; }
|
|
77
|
+
.vc-table th { background: var(--ui-surface-muted); color: var(--ui-text-muted); font-size: 12px; text-align: left; padding: 12px 14px; }
|
|
78
|
+
.vc-table td { border-top: 1px solid var(--ui-border); padding: 14px; font-size: 14px; }
|
|
79
|
+
.vc-admin-shell { display: grid; grid-template-columns: 240px 1fr; min-height: 100vh; }
|
|
80
|
+
.vc-admin-sidebar { background: #0f172a; color: #cbd5e1; padding: 24px 18px; }
|
|
81
|
+
.vc-admin-nav-item { padding: 10px 12px; border-radius: 10px; color: #cbd5e1; }
|
|
82
|
+
.vc-admin-nav-item--active { background: rgba(255,255,255,0.1); color: #fff; }
|
|
83
|
+
.vc-admin-content { padding: 32px; }
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { DslProperties } from '../../../src/generated/schema/dsl-properties.js';
|
|
3
|
+
import type { FontWeight, Justify, Shadow, WidthSize } from '../../../src/generated/schema/dsl-enums.js';
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
Align,
|
|
7
|
+
Axis,
|
|
8
|
+
Border,
|
|
9
|
+
Clip,
|
|
10
|
+
ContentAlign,
|
|
11
|
+
Direction,
|
|
12
|
+
FontWeight,
|
|
13
|
+
HeightSize,
|
|
14
|
+
ImageFit,
|
|
15
|
+
Justify,
|
|
16
|
+
KeyboardType,
|
|
17
|
+
LineLimit,
|
|
18
|
+
TextAlign,
|
|
19
|
+
TextOverflow,
|
|
20
|
+
WidthSize,
|
|
21
|
+
} from '../../../src/generated/schema/dsl-enums.js';
|
|
22
|
+
|
|
23
|
+
export type { DslProperties };
|
|
24
|
+
|
|
25
|
+
export interface ViewAction {
|
|
26
|
+
name: string;
|
|
27
|
+
params?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type DispatchableAction = ViewAction;
|
|
31
|
+
|
|
32
|
+
/** view-contracts view TSX action binding (schema property: onClick / onChange). */
|
|
33
|
+
export type Action = ViewAction;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Preview + sample extension props layered on the DSL catalog.
|
|
37
|
+
* Widens a few catalog types so sample views can use pragmatic literals (e.g. radius={16}, justify="between").
|
|
38
|
+
*/
|
|
39
|
+
export interface CommonProps extends Omit<DslProperties, 'justify' | 'radius' | 'shadow' | 'width' | 'weight' | 'fontWeight'> {
|
|
40
|
+
children?: ReactNode;
|
|
41
|
+
/** View-layer alias for onClick in interactive components. */
|
|
42
|
+
action?: ViewAction;
|
|
43
|
+
justify?: Justify | 'between' | 'around';
|
|
44
|
+
radius?: string | number;
|
|
45
|
+
shadow?: Shadow | boolean;
|
|
46
|
+
width?: WidthSize | number;
|
|
47
|
+
/** Flex grow (number) or sample font weight alias (string) on Text. */
|
|
48
|
+
weight?: number | string;
|
|
49
|
+
fontWeight?: FontWeight | string;
|
|
50
|
+
/** Sample/preview extension props (not in ui-dsl.schema.json). */
|
|
51
|
+
tone?: string;
|
|
52
|
+
size?: number | string;
|
|
53
|
+
ariaLabel?: string;
|
|
54
|
+
testId?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface Option {
|
|
58
|
+
label: string;
|
|
59
|
+
value: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Preview-only field density (not in ui-dsl.schema.json). */
|
|
63
|
+
export type FieldSize = 'sm' | 'md' | 'lg';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Auto-generated component barrel — DO NOT EDIT */
|
|
2
|
+
export * from './extensions.js';
|
|
3
|
+
export type { TableColumn } from './extensions.js';
|
|
4
|
+
export { commonStyle, mapAlign, mapJustify, isHidden, toneClass } from './style.js';
|
|
5
|
+
export type { Action, CommonProps, Option } from './types.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Auto-generated runtime dispatcher — DO NOT EDIT */
|
|
2
|
+
import type { DispatchableAction } from './types.js';
|
|
3
|
+
|
|
4
|
+
type ActionHandler = (action: DispatchableAction) => void | Promise<void>;
|
|
5
|
+
|
|
6
|
+
let handler: ActionHandler | undefined;
|
|
7
|
+
|
|
8
|
+
export function registerActionHandler(nextHandler: ActionHandler): void {
|
|
9
|
+
handler = nextHandler;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function dispatchAction(action: DispatchableAction): Promise<void> {
|
|
13
|
+
if (handler) {
|
|
14
|
+
await handler(action);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (action.name === 'navigate' && typeof action.params?.to === 'string') {
|
|
18
|
+
window.location.href = action.params.to;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
window.dispatchEvent(new CustomEvent('view-contracts:action', { detail: action }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Auto-generated — DO NOT EDIT */
|
|
2
|
+
export interface DispatchableAction {
|
|
3
|
+
name: string;
|
|
4
|
+
params?: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Optional theme context from App element — for future runtime theme switching. */
|
|
8
|
+
export interface AppThemeContext {
|
|
9
|
+
theme?: string;
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// {{screenName}} — SwiftUI scaffold (Phase 5)
|
|
2
|
+
// Theme IR: {{themeJsonPath}}
|
|
3
|
+
// Token IR: {{tokensJsonPath}}
|
|
4
|
+
//
|
|
5
|
+
// TODO(Phase B): Load theme.json and emit PrimaryButtonStyle(), vcTextMuted(), etc.
|
|
6
|
+
|
|
7
|
+
import SwiftUI
|
|
8
|
+
|
|
9
|
+
struct {{screenName}}View: View {
|
|
10
|
+
var body: some View {
|
|
11
|
+
Text("{{screenName}} — SwiftUI scaffold")
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#if DEBUG
|
|
16
|
+
struct {{screenName}}View_Previews: PreviewProvider {
|
|
17
|
+
static var previews: some View {
|
|
18
|
+
{{screenName}}View()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
#endif
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// VcTheme — SwiftUI theme scaffold (Phase 5)
|
|
2
|
+
// Load Token IR + Theme IR from bundle resources:
|
|
3
|
+
// {{tokensJsonPath}}
|
|
4
|
+
// {{themeJsonPath}}
|
|
5
|
+
//
|
|
6
|
+
// TODO(Phase B): Map Theme IR variants to ButtonStyle / ViewModifier implementations.
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
enum VcTheme {
|
|
11
|
+
static let tokensResource = "{{tokensJsonPath}}"
|
|
12
|
+
static let themeResource = "{{themeJsonPath}}"
|
|
13
|
+
|
|
14
|
+
/// Placeholder — decode theme.json into Swift types in Phase B.
|
|
15
|
+
static func loadThemeJson() -> Data? {
|
|
16
|
+
guard let url = Bundle.main.url(forResource: themeResource, withExtension: nil) else {
|
|
17
|
+
return nil
|
|
18
|
+
}
|
|
19
|
+
return try? Data(contentsOf: url)
|
|
20
|
+
}
|
|
21
|
+
}
|
package/spec/ui-dsl.schema.json
CHANGED
|
@@ -227,7 +227,18 @@
|
|
|
227
227
|
"x-category": "validation",
|
|
228
228
|
"x-appliesTo": ["Field", "TextInput", "TextArea", "Select"]
|
|
229
229
|
},
|
|
230
|
-
"variant": {
|
|
230
|
+
"variant": {
|
|
231
|
+
"type": "string",
|
|
232
|
+
"x-category": "visual",
|
|
233
|
+
"x-appliesTo": ["Button", "IconButton", "Box", "Text", "Heading"],
|
|
234
|
+
"x-notes": "design token variant"
|
|
235
|
+
},
|
|
236
|
+
"theme": {
|
|
237
|
+
"type": "string",
|
|
238
|
+
"x-category": "visual",
|
|
239
|
+
"x-appliesTo": ["App"],
|
|
240
|
+
"x-notes": "active theme id for runtime theme switching"
|
|
241
|
+
}
|
|
231
242
|
},
|
|
232
243
|
"elements": {
|
|
233
244
|
"App": {
|
|
@@ -238,7 +249,8 @@
|
|
|
238
249
|
"properties": {
|
|
239
250
|
"id": { "$ref": "#/properties/id" },
|
|
240
251
|
"padding": { "$ref": "#/properties/padding" },
|
|
241
|
-
"direction": { "$ref": "#/properties/direction" }
|
|
252
|
+
"direction": { "$ref": "#/properties/direction" },
|
|
253
|
+
"theme": { "$ref": "#/properties/theme" }
|
|
242
254
|
},
|
|
243
255
|
"_children": { "x-kind": "list", "x-items": ["Page"] }
|
|
244
256
|
},
|
|
@@ -307,8 +319,7 @@
|
|
|
307
319
|
"minWidth": { "$ref": "#/properties/minWidth" },
|
|
308
320
|
"maxWidth": { "$ref": "#/properties/maxWidth" },
|
|
309
321
|
"minHeight": { "$ref": "#/properties/minHeight" },
|
|
310
|
-
"maxHeight": { "$ref": "#/properties/maxHeight" }
|
|
311
|
-
"variant": { "$ref": "#/properties/variant" }
|
|
322
|
+
"maxHeight": { "$ref": "#/properties/maxHeight" }
|
|
312
323
|
},
|
|
313
324
|
"_children": { "x-kind": "any" }
|
|
314
325
|
},
|
|
@@ -328,8 +339,7 @@
|
|
|
328
339
|
"minWidth": { "$ref": "#/properties/minWidth" },
|
|
329
340
|
"maxWidth": { "$ref": "#/properties/maxWidth" },
|
|
330
341
|
"minHeight": { "$ref": "#/properties/minHeight" },
|
|
331
|
-
"maxHeight": { "$ref": "#/properties/maxHeight" }
|
|
332
|
-
"variant": { "$ref": "#/properties/variant" }
|
|
342
|
+
"maxHeight": { "$ref": "#/properties/maxHeight" }
|
|
333
343
|
},
|
|
334
344
|
"_children": { "x-kind": "any" }
|
|
335
345
|
},
|
|
@@ -344,8 +354,7 @@
|
|
|
344
354
|
"padding": { "$ref": "#/properties/padding" },
|
|
345
355
|
"width": { "$ref": "#/properties/width" },
|
|
346
356
|
"height": { "$ref": "#/properties/height" },
|
|
347
|
-
"background": { "$ref": "#/properties/background" }
|
|
348
|
-
"variant": { "$ref": "#/properties/variant" }
|
|
357
|
+
"background": { "$ref": "#/properties/background" }
|
|
349
358
|
},
|
|
350
359
|
"_children": { "x-kind": "any" }
|
|
351
360
|
},
|
|
@@ -368,8 +377,7 @@
|
|
|
368
377
|
"description": "Horizontal or vertical separator",
|
|
369
378
|
"properties": {
|
|
370
379
|
"id": { "$ref": "#/properties/id" },
|
|
371
|
-
"axis": { "$ref": "#/properties/axis" }
|
|
372
|
-
"variant": { "$ref": "#/properties/variant" }
|
|
380
|
+
"axis": { "$ref": "#/properties/axis" }
|
|
373
381
|
},
|
|
374
382
|
"_children": { "x-kind": "none" }
|
|
375
383
|
},
|
|
@@ -430,7 +438,8 @@
|
|
|
430
438
|
"fontWeight": { "$ref": "#/properties/fontWeight" },
|
|
431
439
|
"foreground": { "$ref": "#/properties/foreground" },
|
|
432
440
|
"opacity": { "$ref": "#/properties/opacity" },
|
|
433
|
-
"role": { "$ref": "#/properties/role" }
|
|
441
|
+
"role": { "$ref": "#/properties/role" },
|
|
442
|
+
"variant": { "$ref": "#/properties/variant" }
|
|
434
443
|
},
|
|
435
444
|
"_children": { "x-kind": "text" }
|
|
436
445
|
},
|
|
@@ -445,7 +454,8 @@
|
|
|
445
454
|
"textStyle": { "$ref": "#/properties/textStyle" },
|
|
446
455
|
"textAlign": { "$ref": "#/properties/textAlign" },
|
|
447
456
|
"foreground": { "$ref": "#/properties/foreground" },
|
|
448
|
-
"role": { "$ref": "#/properties/role" }
|
|
457
|
+
"role": { "$ref": "#/properties/role" },
|
|
458
|
+
"variant": { "$ref": "#/properties/variant" }
|
|
449
459
|
},
|
|
450
460
|
"_children": { "x-kind": "text" }
|
|
451
461
|
},
|
|
@@ -718,8 +728,7 @@
|
|
|
718
728
|
"properties": {
|
|
719
729
|
"id": { "$ref": "#/properties/id" },
|
|
720
730
|
"visible": { "$ref": "#/properties/visible" },
|
|
721
|
-
"value": { "$ref": "#/properties/value" }
|
|
722
|
-
"variant": { "$ref": "#/properties/variant" }
|
|
731
|
+
"value": { "$ref": "#/properties/value" }
|
|
723
732
|
},
|
|
724
733
|
"_children": { "x-kind": "none" }
|
|
725
734
|
},
|
|
@@ -729,8 +738,7 @@
|
|
|
729
738
|
"description": "Indeterminate loading",
|
|
730
739
|
"properties": {
|
|
731
740
|
"id": { "$ref": "#/properties/id" },
|
|
732
|
-
"visible": { "$ref": "#/properties/visible" }
|
|
733
|
-
"variant": { "$ref": "#/properties/variant" }
|
|
741
|
+
"visible": { "$ref": "#/properties/visible" }
|
|
734
742
|
},
|
|
735
743
|
"_children": { "x-kind": "none" }
|
|
736
744
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Auto-generated from spec/ui-dsl.schema.json — view-contracts v0.
|
|
1
|
+
/** Auto-generated from spec/ui-dsl.schema.json — view-contracts v0.3.0 — DO NOT EDIT */
|
|
2
2
|
export type Role = "button" | "link" | "heading" | "list" | "listitem" | "main" | "dialog" | "status";
|
|
3
3
|
export type WidthSize = "wrap" | "fill";
|
|
4
4
|
export type HeightSize = "wrap" | "fill";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** Auto-generated from spec/ui-dsl.schema.json — view-contracts v0.
|
|
1
|
+
/** Auto-generated from spec/ui-dsl.schema.json — view-contracts v0.3.0 — DO NOT EDIT */
|
|
2
2
|
export interface ViewAction { name: string; params?: Record<string, unknown> }
|
|
3
|
-
import type { Role, WidthSize, HeightSize, Align, Justify, ContentAlign, Axis, Direction, Scroll, Border, Shadow, TextAlign, LineLimit, TextOverflow, FontWeight, ImageFit, KeyboardType, Clip } from './
|
|
3
|
+
import type { Role, WidthSize, HeightSize, Align, Justify, ContentAlign, Axis, Direction, Scroll, Border, Shadow, TextAlign, LineLimit, TextOverflow, FontWeight, ImageFit, KeyboardType, Clip } from './dsl-enums.js';
|
|
4
4
|
|
|
5
5
|
/** Shared DSL attributes from $defs.properties / properties catalog */
|
|
6
6
|
export interface DslProperties {
|
|
@@ -60,4 +60,5 @@ export interface DslProperties {
|
|
|
60
60
|
error?: string | null;
|
|
61
61
|
help?: string | null;
|
|
62
62
|
variant?: string;
|
|
63
|
+
theme?: string;
|
|
63
64
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Auto-generated from spec/ui-dsl.schema.json — view-contracts v0.
|
|
2
|
-
export * from './
|
|
1
|
+
/** Auto-generated from spec/ui-dsl.schema.json — view-contracts v0.3.0 — DO NOT EDIT */
|
|
2
|
+
export * from './dsl-enums.js';
|
|
3
3
|
export * from './dsl-properties.js';
|
|
4
4
|
export * from './schema-document.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Auto-generated from spec/ui-dsl.schema.json — view-contracts v0.3.0 — DO NOT EDIT */
|
|
2
|
+
/** Property names passed to commonStyle() during React HTML lowering (x-category from DSL + sample extensions). */
|
|
3
|
+
export const LOWERING_STYLE_PROPERTIES = new Set<string>([
|
|
4
|
+
"align",
|
|
5
|
+
"aspectRatio",
|
|
6
|
+
"axis",
|
|
7
|
+
"contentAlign",
|
|
8
|
+
"direction",
|
|
9
|
+
"fontWeight",
|
|
10
|
+
"gap",
|
|
11
|
+
"headingLevel",
|
|
12
|
+
"height",
|
|
13
|
+
"justify",
|
|
14
|
+
"lineLimit",
|
|
15
|
+
"maxHeight",
|
|
16
|
+
"maxWidth",
|
|
17
|
+
"minHeight",
|
|
18
|
+
"minWidth",
|
|
19
|
+
"padding",
|
|
20
|
+
"scroll",
|
|
21
|
+
"selfAlign",
|
|
22
|
+
"textAlign",
|
|
23
|
+
"textOverflow",
|
|
24
|
+
"textStyle",
|
|
25
|
+
"weight",
|
|
26
|
+
"width",
|
|
27
|
+
"size",
|
|
28
|
+
]);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Auto-generated from renderers/react/element-config.meta.json — view-contracts v0.3.0 — DO NOT EDIT */
|
|
2
|
+
/** Shape of entries in renderers/react/elements.yaml (target-specific HTML map, not DSL vocabulary). */
|
|
3
|
+
export interface ReactRendererElementConfig {
|
|
4
|
+
tag: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
ariaLabel?: boolean;
|
|
7
|
+
ariaHidden?: boolean;
|
|
8
|
+
flex?: "row" | "column";
|
|
9
|
+
wrapProp?: string;
|
|
10
|
+
flexGrowProp?: string;
|
|
11
|
+
void?: boolean;
|
|
12
|
+
levelProp?: string;
|
|
13
|
+
defaultLevel?: number;
|
|
14
|
+
actionEvent?: "onClick" | "onSubmit";
|
|
15
|
+
fieldWrap?: boolean;
|
|
16
|
+
inputType?: string;
|
|
17
|
+
sizeClass?: string;
|
|
18
|
+
optionsProp?: string;
|
|
19
|
+
checkbox?: boolean;
|
|
20
|
+
defaultRole?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ReactElementMapFile {
|
|
24
|
+
elements: Record<string, ReactRendererElementConfig>;
|
|
25
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** Auto-generated from spec/ui-dsl.meta.json + ui-dsl.schema.json — view-contracts v0.
|
|
2
|
-
import type { Role, WidthSize, HeightSize, Align, Justify, ContentAlign, Axis, Direction, Scroll, Border, Shadow, TextAlign, LineLimit, TextOverflow, FontWeight, ImageFit, KeyboardType, Clip } from './
|
|
1
|
+
/** Auto-generated from spec/ui-dsl.meta.json + ui-dsl.schema.json — view-contracts v0.3.0 — DO NOT EDIT */
|
|
2
|
+
import type { Role, WidthSize, HeightSize, Align, Justify, ContentAlign, Axis, Direction, Scroll, Border, Shadow, TextAlign, LineLimit, TextOverflow, FontWeight, ImageFit, KeyboardType, Clip } from './dsl-enums.js';
|
|
3
3
|
|
|
4
4
|
export type PropertyType = 'string' | 'number' | 'boolean' | 'array' | 'action' | 'object' | 'null' | string | string[];
|
|
5
5
|
|