view-contracts 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/preview.mjs +61 -62
- package/dist/preview.mjs.map +4 -4
- package/dist/view-contracts.bundle.mjs +232 -174
- package/dist/view-contracts.bundle.mjs.map +4 -4
- package/package.json +1 -1
- package/renderers/react/defaults/theme.yaml +29 -0
- package/renderers/react/defaults/tokens.yaml +61 -1
- package/renderers/react/lowering/lowerToJsx.ts +6 -6
- package/renderers/react/paths.ts +5 -1
- package/renderers/react/runtime/structural.css +38 -0
- package/renderers/react/style/foldLiterals.ts +14 -4
- package/renderers/swiftui/lowering/lowerToSwiftUI.ts +250 -31
- package/renderers/swiftui/lowering/modifiers.ts +90 -11
- package/renderers/swiftui/renderRuntime.ts +48 -0
- package/renderers/swiftui/renderTheme.ts +69 -3
- package/renderers/react/runtime/theme.css +0 -57
|
@@ -49,6 +49,10 @@ function swiftFontWeight(value: string | number | boolean): string | null {
|
|
|
49
49
|
semibold: '.fontWeight(.semibold)',
|
|
50
50
|
medium: '.fontWeight(.medium)',
|
|
51
51
|
regular: '.fontWeight(.regular)',
|
|
52
|
+
'700': '.fontWeight(.bold)',
|
|
53
|
+
'600': '.fontWeight(.semibold)',
|
|
54
|
+
'500': '.fontWeight(.medium)',
|
|
55
|
+
'400': '.fontWeight(.regular)',
|
|
52
56
|
};
|
|
53
57
|
return map[value] ?? null;
|
|
54
58
|
}
|
|
@@ -56,8 +60,15 @@ function swiftFontWeight(value: string | number | boolean): string | null {
|
|
|
56
60
|
/**
|
|
57
61
|
* Convert merged DSL visual props to a single SwiftUI modifier chain.
|
|
58
62
|
*
|
|
59
|
-
*
|
|
63
|
+
* SwiftUI modifier order for fill elements:
|
|
64
|
+
* foreground / font → padding → frame(fill) → background → clipShape → shadow → overlay → frame(fixed) → opacity
|
|
65
|
+
*
|
|
66
|
+
* For non-fill elements:
|
|
60
67
|
* foreground / font → padding → background → clipShape → shadow → overlay → frame → opacity
|
|
68
|
+
*
|
|
69
|
+
* The key insight: .frame(maxWidth: .infinity) MUST come BEFORE .background()
|
|
70
|
+
* so the background fills the expanded area. Otherwise the background only
|
|
71
|
+
* covers the natural content width.
|
|
61
72
|
*/
|
|
62
73
|
export function visualPropSetToModifiers(merged: VisualPropSet): string[] {
|
|
63
74
|
const modifiers: string[] = [];
|
|
@@ -90,7 +101,43 @@ export function visualPropSetToModifiers(merged: VisualPropSet): string[] {
|
|
|
90
101
|
}
|
|
91
102
|
}
|
|
92
103
|
|
|
93
|
-
//
|
|
104
|
+
// Pre-compute frame/size values needed to determine fill vs fixed
|
|
105
|
+
const width = merged.width;
|
|
106
|
+
const height = merged.height;
|
|
107
|
+
const minHeight = merged.minHeight;
|
|
108
|
+
const maxWidth = merged.maxWidth;
|
|
109
|
+
const maxHeight = merged.maxHeight;
|
|
110
|
+
const margin = merged.margin;
|
|
111
|
+
const hasCenteringMargin = margin === '0 auto';
|
|
112
|
+
|
|
113
|
+
const needsFillWidth = width === '100%' || width === 'fill' || maxWidth === 'fill';
|
|
114
|
+
const needsFillHeight = height === '100%' || height === 'fill' || minHeight === '100vh';
|
|
115
|
+
|
|
116
|
+
// 2.5. Fill frame constraints — BEFORE background so bg fills the expanded area
|
|
117
|
+
// alignment: .leading/.top/.topLeading ensures content stays at top-left (matching CSS block behavior)
|
|
118
|
+
const maxWidthPx = maxWidth === 'fill' ? null : (maxWidth !== undefined ? parsePx(maxWidth) : null);
|
|
119
|
+
|
|
120
|
+
if (needsFillWidth && needsFillHeight) {
|
|
121
|
+
if (maxWidthPx !== null) {
|
|
122
|
+
modifiers.push(`.frame(maxWidth: ${maxWidthPx}, maxHeight: .infinity, alignment: .topLeading)`);
|
|
123
|
+
} else {
|
|
124
|
+
modifiers.push('.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)');
|
|
125
|
+
}
|
|
126
|
+
} else if (needsFillWidth) {
|
|
127
|
+
if (maxWidthPx !== null) {
|
|
128
|
+
modifiers.push(`.frame(maxWidth: ${maxWidthPx}, alignment: .leading)`);
|
|
129
|
+
} else {
|
|
130
|
+
modifiers.push('.frame(maxWidth: .infinity, alignment: .leading)');
|
|
131
|
+
}
|
|
132
|
+
} else if (needsFillHeight) {
|
|
133
|
+
modifiers.push('.frame(maxHeight: .infinity, alignment: .top)');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!needsFillHeight && minHeight === '100vh') {
|
|
137
|
+
modifiers.push('.frame(maxHeight: .infinity, alignment: .top)');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 3. Background (fills the frame area — now expanded for fill elements)
|
|
94
141
|
const background = merged.background;
|
|
95
142
|
if (typeof background === 'string' && background !== 'transparent') {
|
|
96
143
|
const color = parseHexColor(background);
|
|
@@ -115,17 +162,49 @@ export function visualPropSetToModifiers(merged: VisualPropSet): string[] {
|
|
|
115
162
|
modifiers.push(`.overlay(RoundedRectangle(cornerRadius: ${radius ?? 0}).stroke(${colorExpr}, lineWidth: ${borderWidth}))`);
|
|
116
163
|
}
|
|
117
164
|
|
|
118
|
-
// 7.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (
|
|
165
|
+
// 7. Non-fill frame constraints (fixed sizes, maxWidth cap without fill)
|
|
166
|
+
if (!needsFillWidth) {
|
|
167
|
+
if (typeof width === 'number') {
|
|
168
|
+
modifiers.push(`.frame(width: ${width})`);
|
|
169
|
+
} else if (width !== undefined && width !== 'wrap') {
|
|
170
|
+
const px = parsePx(width);
|
|
171
|
+
if (px !== null) modifiers.push(`.frame(width: ${px})`);
|
|
172
|
+
}
|
|
173
|
+
if (maxWidth !== undefined && maxWidth !== 'fill') {
|
|
174
|
+
const px = parsePx(maxWidth);
|
|
175
|
+
if (px !== null) modifiers.push(`.frame(maxWidth: ${px})`);
|
|
176
|
+
}
|
|
123
177
|
}
|
|
124
178
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
179
|
+
if (!needsFillHeight) {
|
|
180
|
+
if (typeof height === 'number') {
|
|
181
|
+
modifiers.push(`.frame(height: ${height})`);
|
|
182
|
+
} else if (height !== undefined && height !== 'wrap') {
|
|
183
|
+
const px = parsePx(height);
|
|
184
|
+
if (px !== null) modifiers.push(`.frame(height: ${px})`);
|
|
185
|
+
}
|
|
186
|
+
if (minHeight !== undefined && minHeight !== '100vh') {
|
|
187
|
+
if (typeof minHeight === 'number') {
|
|
188
|
+
modifiers.push(`.frame(minHeight: ${minHeight})`);
|
|
189
|
+
} else {
|
|
190
|
+
const px = parsePx(minHeight);
|
|
191
|
+
if (px !== null) modifiers.push(`.frame(minHeight: ${px})`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (maxHeight !== undefined && maxHeight !== 'fill') {
|
|
195
|
+
const px = parsePx(maxHeight);
|
|
196
|
+
if (px !== null) modifiers.push(`.frame(maxHeight: ${px})`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// margin: 0 auto → center within parent by expanding outer frame (positioning, not sizing)
|
|
201
|
+
if (hasCenteringMargin) {
|
|
202
|
+
modifiers.push('.frame(maxWidth: .infinity, alignment: .center)');
|
|
203
|
+
} else if (typeof margin === 'number') {
|
|
204
|
+
modifiers.push(`.padding(${margin})`);
|
|
205
|
+
} else if (margin !== undefined) {
|
|
206
|
+
const px = parsePx(margin);
|
|
207
|
+
if (px !== null) modifiers.push(`.padding(${px})`);
|
|
129
208
|
}
|
|
130
209
|
|
|
131
210
|
// 8. Opacity (last — affects everything)
|
|
@@ -9,6 +9,51 @@ import { renderSwiftTheme } from './renderTheme.js';
|
|
|
9
9
|
import { renderSwiftComponent } from './renderComponents.js';
|
|
10
10
|
import { loadPreviewMockYaml, previewMockFilePath, pathExists } from '../../src/preview/previewMock.js';
|
|
11
11
|
|
|
12
|
+
const FLOW_LAYOUT_SWIFT = `import SwiftUI
|
|
13
|
+
|
|
14
|
+
/// CSS flex-wrap equivalent: wraps children to next line when horizontal space runs out.
|
|
15
|
+
struct FlowLayout: Layout {
|
|
16
|
+
var spacing: CGFloat = 8
|
|
17
|
+
|
|
18
|
+
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
|
|
19
|
+
let containerWidth = proposal.width ?? .infinity
|
|
20
|
+
var x: CGFloat = 0
|
|
21
|
+
var y: CGFloat = 0
|
|
22
|
+
var rowHeight: CGFloat = 0
|
|
23
|
+
|
|
24
|
+
for subview in subviews {
|
|
25
|
+
let size = subview.sizeThatFits(.unspecified)
|
|
26
|
+
if x + size.width > containerWidth && x > 0 {
|
|
27
|
+
y += rowHeight + spacing
|
|
28
|
+
x = 0
|
|
29
|
+
rowHeight = 0
|
|
30
|
+
}
|
|
31
|
+
x += size.width + spacing
|
|
32
|
+
rowHeight = max(rowHeight, size.height)
|
|
33
|
+
}
|
|
34
|
+
return CGSize(width: containerWidth, height: y + rowHeight)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
|
|
38
|
+
var x = bounds.minX
|
|
39
|
+
var y = bounds.minY
|
|
40
|
+
var rowHeight: CGFloat = 0
|
|
41
|
+
|
|
42
|
+
for subview in subviews {
|
|
43
|
+
let size = subview.sizeThatFits(.unspecified)
|
|
44
|
+
if x + size.width > bounds.maxX && x > bounds.minX {
|
|
45
|
+
y += rowHeight + spacing
|
|
46
|
+
x = bounds.minX
|
|
47
|
+
rowHeight = 0
|
|
48
|
+
}
|
|
49
|
+
subview.place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(size))
|
|
50
|
+
x += size.width + spacing
|
|
51
|
+
rowHeight = max(rowHeight, size.height)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
|
|
12
57
|
function swiftuiTemplatesDir(): string {
|
|
13
58
|
return resolve(packageRoot(), 'renderers/swiftui/templates');
|
|
14
59
|
}
|
|
@@ -33,6 +78,9 @@ export async function renderSwiftUIRuntime(
|
|
|
33
78
|
);
|
|
34
79
|
}
|
|
35
80
|
|
|
81
|
+
// FlowLayout helper (CSS flex-wrap equivalent)
|
|
82
|
+
await writeFile(resolve(swiftuiDir, 'FlowLayout.swift'), FLOW_LAYOUT_SWIFT, 'utf-8');
|
|
83
|
+
|
|
36
84
|
if (compiledDocs && design) {
|
|
37
85
|
for (const partial of compiledDocs.partials) {
|
|
38
86
|
try {
|
|
@@ -72,8 +72,39 @@ function resolveVariantEntries(
|
|
|
72
72
|
case 'radius':
|
|
73
73
|
case 'fontSize':
|
|
74
74
|
case 'minHeight':
|
|
75
|
-
case 'maxWidth':
|
|
76
|
-
|
|
75
|
+
case 'maxWidth': {
|
|
76
|
+
const px = parsePx(value);
|
|
77
|
+
if (px !== null) entries.push({ property, swiftValue: String(px) });
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case 'width': {
|
|
81
|
+
if (value === '100%' || value === 'fill') {
|
|
82
|
+
entries.push({ property: 'widthFill', swiftValue: 'true' });
|
|
83
|
+
} else {
|
|
84
|
+
const px = parsePx(value);
|
|
85
|
+
if (px !== null) entries.push({ property, swiftValue: String(px) });
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case 'height': {
|
|
90
|
+
if (value === '100%' || value === 'fill') {
|
|
91
|
+
entries.push({ property: 'heightFill', swiftValue: 'true' });
|
|
92
|
+
} else {
|
|
93
|
+
const px = parsePx(value);
|
|
94
|
+
if (px !== null) entries.push({ property, swiftValue: String(px) });
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'border': {
|
|
99
|
+
const match = value.match(/^([\d.]+)px/);
|
|
100
|
+
if (match) entries.push({ property: 'borderWidth', swiftValue: String(Number(match[1])) });
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case 'borderColor': {
|
|
104
|
+
const color = swiftColorLiteral(value);
|
|
105
|
+
if (color) entries.push({ property: 'borderColor', swiftValue: color });
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
77
108
|
case 'margin': {
|
|
78
109
|
const px = parsePx(value);
|
|
79
110
|
if (px !== null) entries.push({ property, swiftValue: String(px) });
|
|
@@ -88,14 +119,27 @@ function resolveVariantEntries(
|
|
|
88
119
|
entries.push({ property, swiftValue: value === 'true' || value === 'none' ? 'false' : 'true' });
|
|
89
120
|
break;
|
|
90
121
|
}
|
|
122
|
+
case 'display':
|
|
123
|
+
case 'flexDirection':
|
|
124
|
+
case 'gridTemplateColumns':
|
|
125
|
+
case 'alignItems':
|
|
126
|
+
case 'justifyContent':
|
|
127
|
+
case 'typography':
|
|
128
|
+
// Consumed at compile time by lowerToSwiftUI.ts (container type / alignment selection).
|
|
129
|
+
// Not representable as a runtime VcVariantStyle field.
|
|
130
|
+
break;
|
|
91
131
|
default:
|
|
132
|
+
console.warn(
|
|
133
|
+
`[view-contracts] SwiftUI theme: variant property "${property}" is not handled ` +
|
|
134
|
+
`(value: "${value}"). Add a case to renderTheme.ts resolveVariantEntries().`,
|
|
135
|
+
);
|
|
92
136
|
break;
|
|
93
137
|
}
|
|
94
138
|
}
|
|
95
139
|
return entries;
|
|
96
140
|
}
|
|
97
141
|
|
|
98
|
-
const STRUCT_FIELD_ORDER = ['foreground', 'background', 'padding', 'paddingH', 'paddingV', 'radius', 'fontSize', 'fontWeight', 'shadow', 'minHeight', 'maxWidth'];
|
|
142
|
+
const STRUCT_FIELD_ORDER = ['foreground', 'background', 'padding', 'paddingH', 'paddingV', 'widthFill', 'heightFill', 'radius', 'fontSize', 'fontWeight', 'shadow', 'borderWidth', 'borderColor', 'minHeight', 'maxWidth'];
|
|
99
143
|
|
|
100
144
|
function emitVariantStyleInit(entries: ResolvedEntry[], indent: string): string {
|
|
101
145
|
if (entries.length === 0) return `${indent}VcVariantStyle()`;
|
|
@@ -133,16 +177,21 @@ export function renderSwiftTheme(theme: ThemeIR, tokens: TokenIR): string {
|
|
|
133
177
|
lines.push(` var padding: CGFloat?`);
|
|
134
178
|
lines.push(` var paddingH: CGFloat?`);
|
|
135
179
|
lines.push(` var paddingV: CGFloat?`);
|
|
180
|
+
lines.push(` var widthFill: Bool?`);
|
|
181
|
+
lines.push(` var heightFill: Bool?`);
|
|
136
182
|
lines.push(` var radius: CGFloat?`);
|
|
137
183
|
lines.push(` var fontSize: CGFloat?`);
|
|
138
184
|
lines.push(` var fontWeight: Font.Weight?`);
|
|
139
185
|
lines.push(` var shadow: Bool?`);
|
|
186
|
+
lines.push(` var borderWidth: CGFloat?`);
|
|
187
|
+
lines.push(` var borderColor: Color?`);
|
|
140
188
|
lines.push(` var minHeight: CGFloat?`);
|
|
141
189
|
lines.push(` var maxWidth: CGFloat?`);
|
|
142
190
|
lines.push(`}`);
|
|
143
191
|
lines.push(``);
|
|
144
192
|
|
|
145
193
|
// Nil-transparent: only apply properties the style actually defines.
|
|
194
|
+
// frame(maxWidth/maxHeight: .infinity) MUST come BEFORE background so bg fills expanded area.
|
|
146
195
|
lines.push(`extension View {`);
|
|
147
196
|
lines.push(` func applyVcStyle(_ s: VcVariantStyle) -> some View {`);
|
|
148
197
|
lines.push(` self`);
|
|
@@ -151,14 +200,31 @@ export function renderSwiftTheme(theme: ThemeIR, tokens: TokenIR): string {
|
|
|
151
200
|
lines.push(` .ifSet(s.padding) { $0.padding($1) }`);
|
|
152
201
|
lines.push(` .ifSet(s.paddingH) { $0.padding(.horizontal, $1) }`);
|
|
153
202
|
lines.push(` .ifSet(s.paddingV) { $0.padding(.vertical, $1) }`);
|
|
203
|
+
lines.push(` .vcFillFrame(widthFill: s.widthFill ?? false, heightFill: s.heightFill ?? false)`);
|
|
154
204
|
lines.push(` .ifSet(s.background) { $0.background($1) }`);
|
|
155
205
|
lines.push(` .ifSet(s.radius) { $0.clipShape(RoundedRectangle(cornerRadius: $1)) }`);
|
|
206
|
+
lines.push(` .ifSet(s.borderColor) { $0.overlay(RoundedRectangle(cornerRadius: s.radius ?? 0).stroke($1, lineWidth: s.borderWidth ?? 1)) }`);
|
|
156
207
|
lines.push(` .ifSet(s.shadow) { v, on in on ? AnyView(v.shadow(color: Color.black.opacity(0.08), radius: 4, x: 0, y: 2)) : AnyView(v) }`);
|
|
157
208
|
lines.push(` }`);
|
|
158
209
|
lines.push(`}`);
|
|
159
210
|
lines.push(``);
|
|
160
211
|
lines.push(`extension View {`);
|
|
161
212
|
lines.push(` @ViewBuilder`);
|
|
213
|
+
lines.push(` func vcFillFrame(widthFill: Bool, heightFill: Bool) -> some View {`);
|
|
214
|
+
lines.push(` if widthFill && heightFill {`);
|
|
215
|
+
lines.push(` self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)`);
|
|
216
|
+
lines.push(` } else if widthFill {`);
|
|
217
|
+
lines.push(` self.frame(maxWidth: .infinity, alignment: .leading)`);
|
|
218
|
+
lines.push(` } else if heightFill {`);
|
|
219
|
+
lines.push(` self.frame(maxHeight: .infinity, alignment: .top)`);
|
|
220
|
+
lines.push(` } else {`);
|
|
221
|
+
lines.push(` self`);
|
|
222
|
+
lines.push(` }`);
|
|
223
|
+
lines.push(` }`);
|
|
224
|
+
lines.push(`}`);
|
|
225
|
+
lines.push(``);
|
|
226
|
+
lines.push(`extension View {`);
|
|
227
|
+
lines.push(` @ViewBuilder`);
|
|
162
228
|
lines.push(` func ifSet<T, V: View>(_ value: T?, _ transform: (Self, T) -> V) -> some View {`);
|
|
163
229
|
lines.push(` if let value { transform(self, value) } else { self }`);
|
|
164
230
|
lines.push(` }`);
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
* { box-sizing: border-box; }
|
|
2
|
-
body { margin: 0; background: var(--ui-bg); color: var(--ui-text); font-family: var(--ui-font); }
|
|
3
|
-
button, input, textarea, select { font: inherit; }
|
|
4
|
-
|
|
5
|
-
.vc-page { min-height: 100vh; }
|
|
6
|
-
.vc-box { display: block; }
|
|
7
|
-
.vc-column { display: flex; flex-direction: column; }
|
|
8
|
-
.vc-stack { display: flex; flex-direction: column; }
|
|
9
|
-
.vc-row { display: flex; flex-direction: row; }
|
|
10
|
-
.vc-wrap { flex-wrap: wrap; }
|
|
11
|
-
.vc-spacer { min-width: 0; min-height: 0; }
|
|
12
|
-
.vc-divider { border: 0; border-top: 1px solid var(--ui-border); width: 100%; }
|
|
13
|
-
|
|
14
|
-
.vc-heading { margin: 0; line-height: 1.2; letter-spacing: -0.02em; }
|
|
15
|
-
.vc-heading-1 { font-size: 32px; }
|
|
16
|
-
.vc-heading-2 { font-size: 22px; }
|
|
17
|
-
.vc-heading-3 { font-size: 18px; }
|
|
18
|
-
.vc-heading-4 { font-size: 15px; }
|
|
19
|
-
.vc-text { margin: 0; line-height: 1.6; }
|
|
20
|
-
.vc-link { color: var(--ui-primary); text-decoration: none; }
|
|
21
|
-
.vc-link:hover { text-decoration: underline; }
|
|
22
|
-
|
|
23
|
-
.vc-button { border: 0; cursor: pointer; }
|
|
24
|
-
|
|
25
|
-
.vc-form { display: flex; flex-direction: column; }
|
|
26
|
-
.vc-field { display: flex; flex-direction: column; gap: 6px; }
|
|
27
|
-
.vc-field-label { font-size: 13px; font-weight: 700; color: var(--ui-text); }
|
|
28
|
-
.vc-field-help { font-size: 12px; color: var(--ui-text-muted); }
|
|
29
|
-
.vc-field-error { font-size: 12px; color: var(--ui-danger); }
|
|
30
|
-
.vc-text-input, .vc-text-area { width: 100%; outline: none; }
|
|
31
|
-
.vc-text-input:focus, .vc-text-area:focus { border-color: var(--ui-primary); box-shadow: 0 0 0 3px var(--ui-primary-weak); }
|
|
32
|
-
.vc-field-sm { min-height: 34px; padding: 6px 10px; }
|
|
33
|
-
.vc-field-md { min-height: 42px; }
|
|
34
|
-
.vc-field-lg { min-height: 50px; padding: 12px 14px; }
|
|
35
|
-
.vc-select {
|
|
36
|
-
width: 100%;
|
|
37
|
-
outline: none;
|
|
38
|
-
appearance: none;
|
|
39
|
-
-webkit-appearance: none;
|
|
40
|
-
-moz-appearance: none;
|
|
41
|
-
cursor: pointer;
|
|
42
|
-
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6l4 4 4-4' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
43
|
-
background-repeat: no-repeat;
|
|
44
|
-
background-position: right 12px center;
|
|
45
|
-
background-size: 16px;
|
|
46
|
-
}
|
|
47
|
-
.vc-select:focus { border-color: var(--ui-primary); box-shadow: 0 0 0 3px var(--ui-primary-weak); }
|
|
48
|
-
.vc-text-area { resize: vertical; }
|
|
49
|
-
.vc-checkbox { display: inline-flex; align-items: center; gap: 8px; }
|
|
50
|
-
.vc-list { margin: 0; padding-left: 20px; }
|
|
51
|
-
.vc-list-item { margin: 0; padding: 2px 0; }
|
|
52
|
-
|
|
53
|
-
.vc-table { width: 100%; display: flex; flex-direction: column; }
|
|
54
|
-
.vc-table-header { display: flex; background: var(--ui-surface-muted, #f8f9fa); }
|
|
55
|
-
.vc-table-column { text-align: left; padding: 12px 14px; font-size: 12px; font-weight: 600; color: var(--ui-text-muted, #6b7280); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
|
|
56
|
-
.vc-table-row { display: flex; align-items: center; border-top: 1px solid var(--ui-border, #e5e7eb); }
|
|
57
|
-
.vc-table-cell { padding: 14px; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
|