@dataverse-kit/surface-kit 0.1.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/dist/index.mjs ADDED
@@ -0,0 +1,1122 @@
1
+ // src/core/sizePresets.ts
2
+ var DIALOG_SIZE_PX = {
3
+ sm: 600,
4
+ md: 900,
5
+ lg: 1200,
6
+ xl: 1600
7
+ };
8
+ function dialogWidthPx(opts = {}) {
9
+ if (typeof opts.width === "number") return opts.width;
10
+ const size = opts.size ?? "lg";
11
+ if (size === "fullscreen") return DIALOG_SIZE_PX.xl;
12
+ return DIALOG_SIZE_PX[size];
13
+ }
14
+ function dialogWidthCss(opts = {}) {
15
+ if (opts.size === "fullscreen" && opts.width == null) return "95vw";
16
+ return `${dialogWidthPx(opts)}px`;
17
+ }
18
+ function dialogHeightCss(height) {
19
+ return height != null && height <= 100 ? `${height}vh` : "95vh";
20
+ }
21
+ var MODAL_AUTOSIZE_MIN_WIDTH = 1e3;
22
+ var PANEL_DEFAULT_WIDTH = 480;
23
+ function panelKindFromWidth(width) {
24
+ return (width ?? PANEL_DEFAULT_WIDTH) > 500 ? "medium" : "custom";
25
+ }
26
+ function panelCustomWidthPx(width) {
27
+ return width ?? PANEL_DEFAULT_WIDTH;
28
+ }
29
+ var CALLOUT_DEFAULT_WIDTH = 320;
30
+ var calloutFormDefaults = {
31
+ width: CALLOUT_DEFAULT_WIDTH,
32
+ directionalHint: "bottomLeftEdge"
33
+ };
34
+ function calloutWidthPx(width) {
35
+ return width ?? CALLOUT_DEFAULT_WIDTH;
36
+ }
37
+
38
+ // src/core/columns.ts
39
+ function gridTemplateForColumns(columns) {
40
+ return `repeat(${columns}, 1fr)`;
41
+ }
42
+ function clampSpan(span, columns) {
43
+ if (!Number.isFinite(span)) return 1;
44
+ return Math.max(1, Math.min(Math.round(span), columns));
45
+ }
46
+
47
+ // src/responsive/breakpoints.ts
48
+ var BREAKPOINTS = {
49
+ xs: 0,
50
+ sm: 480,
51
+ md: 768,
52
+ lg: 1024,
53
+ xl: 1366
54
+ };
55
+ var CARD_REFLOW_WIDTH = 900;
56
+ var DESCENDING = ["xl", "lg", "md", "sm", "xs"];
57
+ var ASCENDING = ["xs", "sm", "md", "lg", "xl"];
58
+ function resolveBreakpoint(width) {
59
+ for (const bp of DESCENDING) {
60
+ if (width >= BREAKPOINTS[bp]) return bp;
61
+ }
62
+ return "xs";
63
+ }
64
+ function isResponsiveMap(v) {
65
+ return typeof v === "object" && v !== null && ASCENDING.some((bp) => bp in v);
66
+ }
67
+ function resolveResponsiveValue(value, breakpoint) {
68
+ if (!isResponsiveMap(value)) return value;
69
+ const map = value;
70
+ const idx = ASCENDING.indexOf(breakpoint);
71
+ for (let i = idx; i >= 0; i--) {
72
+ const v = map[ASCENDING[i]];
73
+ if (v !== void 0) return v;
74
+ }
75
+ for (let i = idx + 1; i < ASCENDING.length; i++) {
76
+ const v = map[ASCENDING[i]];
77
+ if (v !== void 0) return v;
78
+ }
79
+ return void 0;
80
+ }
81
+
82
+ // src/responsive/builders.ts
83
+ function atRule(mode, maxPx) {
84
+ return mode === "container" ? `@container (max-width: ${maxPx}px)` : `@media (max-width: ${maxPx}px)`;
85
+ }
86
+ function fieldGridStyle(columns, mode = "media") {
87
+ const capped = Math.min(columns, 2);
88
+ return {
89
+ display: "grid",
90
+ gridTemplateColumns: gridTemplateForColumns(columns),
91
+ gap: "4px 24px",
92
+ selectors: {
93
+ [atRule(mode, BREAKPOINTS.md)]: { gridTemplateColumns: gridTemplateForColumns(capped) },
94
+ [atRule(mode, BREAKPOINTS.sm)]: { gridTemplateColumns: "1fr" }
95
+ }
96
+ };
97
+ }
98
+ function sectionStackingStyle(gridTemplateColumns, mode = "media") {
99
+ return {
100
+ display: "grid",
101
+ gridTemplateColumns,
102
+ gap: 16,
103
+ alignItems: "start",
104
+ selectors: {
105
+ [atRule(mode, BREAKPOINTS.md)]: { gridTemplateColumns: "1fr" }
106
+ }
107
+ };
108
+ }
109
+ function fieldRowStyle(labelWidth = "130px", mode = "media") {
110
+ return {
111
+ display: "grid",
112
+ gridTemplateColumns: `${labelWidth} 1fr`,
113
+ alignItems: "center",
114
+ gap: 8,
115
+ minHeight: 38,
116
+ selectors: {
117
+ [atRule(mode, BREAKPOINTS.sm)]: { gridTemplateColumns: "1fr", alignItems: "flex-start", minHeight: 0, gap: 4 }
118
+ }
119
+ };
120
+ }
121
+
122
+ // src/tokens/tokens.ts
123
+ var colors = {
124
+ brand: "#0078d4",
125
+ brandHover: "#106ebe",
126
+ brandDark: "#005a9e",
127
+ borderNeutral: "#edebe9",
128
+ textPrimary: "#323130",
129
+ textSecondary: "#605e5c",
130
+ surface: "#ffffff",
131
+ surfaceAlt: "#f5f5f5",
132
+ // App canvas behind cards/forms (matches the Dynamics model-driven form background).
133
+ canvas: "#fafafa",
134
+ // Required-field asterisk / error accent (Fluent error red).
135
+ danger: "#a4262c",
136
+ // Field input: subtle idle fill + neutral border (Dynamics-style).
137
+ fieldFill: "#faf9f8",
138
+ fieldFillReadonly: "#f3f2f1",
139
+ fieldBorder: "#e1dfdd"
140
+ };
141
+ var spacing = {
142
+ xs: 4,
143
+ sm: 8,
144
+ md: 12,
145
+ lg: 16,
146
+ xl: 24,
147
+ xxl: 40
148
+ };
149
+ var typography = {
150
+ fontFamily: '"Segoe UI", Arial, sans-serif',
151
+ fontFamilySemibold: '"Segoe UI Semibold", "Segoe UI", sans-serif',
152
+ fontSize: {
153
+ sm: "12px",
154
+ smPlus: "13px",
155
+ md: "14px",
156
+ lg: "17px"
157
+ },
158
+ fontWeight: {
159
+ regular: 400,
160
+ semibold: 600
161
+ }
162
+ };
163
+ var radius = {
164
+ card: "4px",
165
+ // Softer rounding for the outer form shell (matches Dynamics cards).
166
+ lg: "8px"
167
+ };
168
+ var layout = {
169
+ // Width of the label column in a two-column "Label : input" field row.
170
+ labelColWidth: "130px"
171
+ };
172
+
173
+ // src/tokens/theme.ts
174
+ import { createTheme } from "@fluentui/react";
175
+ function createDynamicsTheme() {
176
+ return createTheme({
177
+ palette: {
178
+ themePrimary: colors.brand,
179
+ themeDarkAlt: colors.brandHover,
180
+ themeDark: colors.brandDark,
181
+ themeDarker: colors.brandDark,
182
+ neutralPrimary: colors.textPrimary,
183
+ neutralSecondary: colors.textSecondary,
184
+ neutralLight: colors.borderNeutral,
185
+ neutralLighter: colors.surfaceAlt,
186
+ white: colors.surface,
187
+ redDark: colors.danger
188
+ },
189
+ defaultFontStyle: {
190
+ fontFamily: typography.fontFamily
191
+ },
192
+ semanticColors: {
193
+ errorText: colors.danger,
194
+ inputBorder: colors.fieldBorder,
195
+ inputBackground: colors.fieldFill,
196
+ bodyBackground: colors.surface
197
+ }
198
+ });
199
+ }
200
+
201
+ // src/tokens/density.ts
202
+ var densityScale = {
203
+ comfortable: 1,
204
+ compact: 0.75
205
+ };
206
+ function scaleSpacing(value, density = "comfortable") {
207
+ return Math.round(value * densityScale[density]);
208
+ }
209
+
210
+ // src/hooks/useContainerSize.ts
211
+ import { useState, useEffect } from "react";
212
+ function useContainerSize(ref) {
213
+ const [size, setSize] = useState({ width: 0, height: 0, inlineSize: 0, blockSize: 0 });
214
+ useEffect(() => {
215
+ if (!ref.current || typeof ResizeObserver === "undefined") return;
216
+ const observer = new ResizeObserver((entries) => {
217
+ for (const entry of entries) {
218
+ const { width, height } = entry.contentRect;
219
+ const inlineSize = entry.borderBoxSize?.[0]?.inlineSize ?? width;
220
+ const blockSize = entry.borderBoxSize?.[0]?.blockSize ?? height;
221
+ setSize({ width, height, inlineSize, blockSize });
222
+ }
223
+ });
224
+ observer.observe(ref.current);
225
+ return () => observer.disconnect();
226
+ }, [ref]);
227
+ return size;
228
+ }
229
+
230
+ // src/hooks/useContainerWidth.ts
231
+ import { useRef } from "react";
232
+ function useContainerWidth() {
233
+ const ref = useRef(null);
234
+ const { width } = useContainerSize(ref);
235
+ return [ref, width];
236
+ }
237
+
238
+ // src/hooks/useContainerBreakpoint.ts
239
+ function useContainerBreakpoint() {
240
+ const [ref, width] = useContainerWidth();
241
+ return [ref, resolveBreakpoint(width), width];
242
+ }
243
+
244
+ // src/hooks/useResponsiveValue.ts
245
+ function useResponsiveValue(value) {
246
+ const [ref, breakpoint] = useContainerBreakpoint();
247
+ return [ref, resolveResponsiveValue(value, breakpoint)];
248
+ }
249
+
250
+ // src/hooks/useMediaQuery.ts
251
+ import { useState as useState2, useEffect as useEffect2 } from "react";
252
+ function useMediaQuery(query) {
253
+ const [matches, setMatches] = useState2(() => {
254
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
255
+ return window.matchMedia(query).matches;
256
+ });
257
+ useEffect2(() => {
258
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
259
+ const mql = window.matchMedia(query);
260
+ const onChange = () => setMatches(mql.matches);
261
+ onChange();
262
+ mql.addEventListener("change", onChange);
263
+ return () => mql.removeEventListener("change", onChange);
264
+ }, [query]);
265
+ return matches;
266
+ }
267
+
268
+ // src/layouts/ColumnLayout.tsx
269
+ import { mergeStyles } from "@fluentui/react";
270
+ import { jsx } from "react/jsx-runtime";
271
+ var ColumnLayout = ({
272
+ columns,
273
+ gap = "4px 24px",
274
+ responsive = true,
275
+ mode = "media",
276
+ className,
277
+ style,
278
+ children
279
+ }) => {
280
+ const base = responsive ? fieldGridStyle(columns, mode) : { display: "grid", gridTemplateColumns: gridTemplateForColumns(columns) };
281
+ const cls = mergeStyles(base, { gap }, className);
282
+ return /* @__PURE__ */ jsx("div", { className: cls, style, children });
283
+ };
284
+
285
+ // src/layouts/TwoColumnLayout.tsx
286
+ import { mergeStyles as mergeStyles2 } from "@fluentui/react";
287
+ import { jsx as jsx2 } from "react/jsx-runtime";
288
+ var TwoColumnLayout = ({
289
+ gap = 16,
290
+ collapseBelow = "sm",
291
+ className,
292
+ style,
293
+ children
294
+ }) => {
295
+ const cls = mergeStyles2(
296
+ {
297
+ display: "grid",
298
+ gridTemplateColumns: "1fr 1fr",
299
+ gap,
300
+ alignItems: "start",
301
+ selectors: {
302
+ [`@media (max-width: ${BREAKPOINTS[collapseBelow]}px)`]: { gridTemplateColumns: "1fr" }
303
+ }
304
+ },
305
+ className
306
+ );
307
+ return /* @__PURE__ */ jsx2("div", { className: cls, style, children });
308
+ };
309
+
310
+ // src/layouts/FieldRow.tsx
311
+ import { mergeStyleSets } from "@fluentui/react";
312
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
313
+ var FieldRow = ({
314
+ label,
315
+ required,
316
+ fullWidth,
317
+ labelWidth = "130px",
318
+ labelPosition = "beside",
319
+ className,
320
+ children
321
+ }) => {
322
+ const styles = mergeStyleSets({
323
+ // The beside layout IS the shared responsive builder (130px label grid + @480
324
+ // stack) — no hand-written breakpoint here.
325
+ rowBeside: fieldRowStyle(labelWidth, "media"),
326
+ rowAbove: {
327
+ display: "grid",
328
+ gridTemplateColumns: "1fr",
329
+ gap: 2,
330
+ minHeight: 38
331
+ },
332
+ label: {
333
+ fontSize: typography.fontSize.sm,
334
+ fontWeight: typography.fontWeight.semibold,
335
+ color: colors.textPrimary,
336
+ fontFamily: typography.fontFamily,
337
+ lineHeight: "1.3",
338
+ wordBreak: "break-word"
339
+ },
340
+ required: { color: colors.danger, marginLeft: 2, fontWeight: typography.fontWeight.semibold },
341
+ full: { gridColumn: "1 / -1" }
342
+ });
343
+ const cls = [
344
+ labelPosition === "above" ? styles.rowAbove : styles.rowBeside,
345
+ fullWidth ? styles.full : "",
346
+ className
347
+ ].filter(Boolean).join(" ");
348
+ return /* @__PURE__ */ jsxs("div", { className: cls, children: [
349
+ label != null && /* @__PURE__ */ jsxs("span", { className: styles.label, children: [
350
+ label,
351
+ required && /* @__PURE__ */ jsx3("span", { className: styles.required, children: "*" })
352
+ ] }),
353
+ children
354
+ ] });
355
+ };
356
+
357
+ // src/containers/FormSurface.tsx
358
+ import { mergeStyles as mergeStyles3 } from "@fluentui/react";
359
+
360
+ // src/internal/styles.ts
361
+ var sharedTitleStyle = {
362
+ fontWeight: typography.fontWeight.semibold,
363
+ fontSize: typography.fontSize.md,
364
+ color: colors.textPrimary,
365
+ fontFamily: typography.fontFamilySemibold,
366
+ paddingBottom: 0,
367
+ marginBottom: 0
368
+ };
369
+ var dynamicsCardStyle = {
370
+ background: colors.surface,
371
+ border: `1px solid ${colors.borderNeutral}`,
372
+ borderRadius: radius.card,
373
+ overflow: "hidden"
374
+ };
375
+ var cardShadow = "0 1.6px 3.6px rgba(0, 0, 0, 0.08), 0 0.3px 0.9px rgba(0, 0, 0, 0.05)";
376
+ function stackChildrenBelow(maxPx) {
377
+ return {
378
+ selectors: {
379
+ [`@media (max-width: ${maxPx}px)`]: {
380
+ selectors: { "> *": { gridColumn: "1 / -1" } }
381
+ }
382
+ }
383
+ };
384
+ }
385
+
386
+ // src/containers/FormSurface.tsx
387
+ import { jsx as jsx4 } from "react/jsx-runtime";
388
+ var FormSurface = ({
389
+ columns = 12,
390
+ gap = 20,
391
+ padding = 20,
392
+ background = colors.canvas,
393
+ containerWidth,
394
+ collapseBelow = "sm",
395
+ maxWidth,
396
+ className,
397
+ style,
398
+ children
399
+ }) => {
400
+ const collapsePx = BREAKPOINTS[collapseBelow];
401
+ const forceStack = containerWidth != null && containerWidth <= collapsePx;
402
+ const cls = mergeStyles3(
403
+ {
404
+ display: "grid",
405
+ gridTemplateColumns: forceStack ? "1fr" : `repeat(${columns}, 1fr)`,
406
+ gap,
407
+ padding,
408
+ background,
409
+ alignItems: "start",
410
+ ...maxWidth != null ? { maxWidth, marginLeft: "auto", marginRight: "auto" } : {},
411
+ // When the container forces a single column, reset child spans the same way the
412
+ // viewport fallback does — otherwise a Section with `columnStart` references grid
413
+ // lines that no longer exist and escapes the stack. (Mutually exclusive with the
414
+ // @media fallback below, which only applies when containerWidth is unset.)
415
+ ...forceStack ? { selectors: { "> *": { gridColumn: "1 / -1" } } } : {},
416
+ // Viewport fallback only when the container width isn't explicitly provided.
417
+ ...containerWidth == null ? stackChildrenBelow(collapsePx) : {}
418
+ },
419
+ className
420
+ );
421
+ return /* @__PURE__ */ jsx4("div", { className: cls, style, children });
422
+ };
423
+
424
+ // src/containers/PageSurface.tsx
425
+ import { jsx as jsx5 } from "react/jsx-runtime";
426
+ var PageSurface = ({ centered, maxWidth, ...rest }) => /* @__PURE__ */ jsx5(FormSurface, { maxWidth: maxWidth ?? (centered ? 1200 : void 0), ...rest });
427
+
428
+ // src/containers/Section.tsx
429
+ import { useState as useState3 } from "react";
430
+ import { mergeStyleSets as mergeStyleSets3 } from "@fluentui/react";
431
+
432
+ // src/containers/SectionHeader.tsx
433
+ import { mergeStyleSets as mergeStyleSets2, CommandBar, Icon } from "@fluentui/react";
434
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
435
+ var SectionHeader = ({
436
+ title,
437
+ statusText,
438
+ showCommandBar,
439
+ commandBarItems = [],
440
+ actions,
441
+ collapsible,
442
+ collapsed,
443
+ onToggle
444
+ }) => {
445
+ const styles = mergeStyleSets2({
446
+ root: {
447
+ display: "flex",
448
+ justifyContent: "space-between",
449
+ alignItems: "center",
450
+ flexWrap: "nowrap",
451
+ borderBottom: `1px solid ${colors.borderNeutral}`,
452
+ marginBottom: 16,
453
+ minHeight: 48,
454
+ width: "100%"
455
+ },
456
+ left: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 },
457
+ title: sharedTitleStyle,
458
+ status: { marginLeft: 8, fontSize: "0.9em", color: colors.textSecondary },
459
+ chevron: {
460
+ cursor: "pointer",
461
+ border: "none",
462
+ background: "transparent",
463
+ padding: 4,
464
+ display: "flex",
465
+ alignItems: "center",
466
+ color: colors.textSecondary
467
+ }
468
+ });
469
+ return /* @__PURE__ */ jsxs2("div", { className: styles.root, children: [
470
+ /* @__PURE__ */ jsxs2("div", { className: styles.left, children: [
471
+ collapsible && /* @__PURE__ */ jsx6(
472
+ "button",
473
+ {
474
+ type: "button",
475
+ className: styles.chevron,
476
+ onClick: onToggle,
477
+ "aria-label": collapsed ? "Expand section" : "Collapse section",
478
+ "aria-expanded": !collapsed,
479
+ children: /* @__PURE__ */ jsx6(Icon, { iconName: collapsed ? "ChevronRight" : "ChevronDown" })
480
+ }
481
+ ),
482
+ title && /* @__PURE__ */ jsx6("span", { className: styles.title, children: title }),
483
+ statusText && /* @__PURE__ */ jsx6("span", { className: styles.status, children: statusText })
484
+ ] }),
485
+ actions ? /* @__PURE__ */ jsx6("div", { children: actions }) : showCommandBar && commandBarItems.length > 0 ? /* @__PURE__ */ jsx6(
486
+ CommandBar,
487
+ {
488
+ items: commandBarItems.slice(0, 2),
489
+ overflowItems: commandBarItems.slice(2),
490
+ styles: { root: { background: "transparent", padding: 0, height: "auto" } }
491
+ }
492
+ ) : null
493
+ ] });
494
+ };
495
+
496
+ // src/containers/Section.tsx
497
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
498
+ var Section = ({
499
+ title,
500
+ columnSpan = 12,
501
+ columnStart,
502
+ rowPosition,
503
+ internalColumns = 12,
504
+ showCommandBar,
505
+ commandBarItems,
506
+ statusText,
507
+ notification,
508
+ collapsible,
509
+ defaultCollapsed = false,
510
+ collapsed: controlledCollapsed,
511
+ onCollapsedChange,
512
+ variant = "card",
513
+ minHeight = 80,
514
+ className,
515
+ style,
516
+ children
517
+ }) => {
518
+ const [internalCollapsed, setInternalCollapsed] = useState3(defaultCollapsed);
519
+ const collapsed = controlledCollapsed ?? internalCollapsed;
520
+ const toggle = () => {
521
+ const next = !collapsed;
522
+ if (controlledCollapsed == null) setInternalCollapsed(next);
523
+ onCollapsedChange?.(next);
524
+ };
525
+ const gridColumn = columnStart ? `${columnStart} / span ${columnSpan}` : `span ${columnSpan}`;
526
+ const rootStyle = {
527
+ gridColumn,
528
+ ...rowPosition ? { gridRowStart: rowPosition } : {},
529
+ ...style
530
+ };
531
+ const styles = mergeStyleSets3({
532
+ card: { ...dynamicsCardStyle, boxShadow: cardShadow, padding: "3px 20px 20px 20px", minWidth: 300, width: "100%" },
533
+ flat: {
534
+ background: colors.surface,
535
+ borderRadius: radius.card,
536
+ padding: "3px 20px 20px 20px",
537
+ minWidth: 300,
538
+ width: "100%",
539
+ overflow: "hidden"
540
+ },
541
+ placeholder: {
542
+ border: `1px dashed ${colors.borderNeutral}`,
543
+ borderRadius: radius.card,
544
+ minHeight,
545
+ width: "100%",
546
+ background: "transparent"
547
+ },
548
+ notification: { marginBottom: 8 },
549
+ body: {
550
+ display: "grid",
551
+ gridTemplateColumns: `repeat(${internalColumns}, 1fr)`,
552
+ gap: 7,
553
+ alignItems: "start",
554
+ selectors: {
555
+ [`@media (max-width: ${BREAKPOINTS.sm}px)`]: { selectors: { "> *": { gridColumn: "1 / -1" } } }
556
+ }
557
+ }
558
+ });
559
+ if (variant === "placeholder") {
560
+ return /* @__PURE__ */ jsx7("div", { className: [styles.placeholder, className].filter(Boolean).join(" "), style: rootStyle });
561
+ }
562
+ const showHeader = Boolean(title) || showCommandBar || collapsible;
563
+ return /* @__PURE__ */ jsxs3("div", { className: [variant === "flat" ? styles.flat : styles.card, className].filter(Boolean).join(" "), style: rootStyle, children: [
564
+ notification && /* @__PURE__ */ jsx7("div", { className: styles.notification, children: notification }),
565
+ showHeader && /* @__PURE__ */ jsx7(
566
+ SectionHeader,
567
+ {
568
+ title,
569
+ statusText,
570
+ showCommandBar,
571
+ commandBarItems,
572
+ collapsible,
573
+ collapsed,
574
+ onToggle: toggle
575
+ }
576
+ ),
577
+ !collapsed && /* @__PURE__ */ jsx7("div", { className: styles.body, children })
578
+ ] });
579
+ };
580
+
581
+ // src/containers/SectionCard.tsx
582
+ import { jsx as jsx8 } from "react/jsx-runtime";
583
+ var SectionCard = ({
584
+ accentColor = colors.brand,
585
+ accentWidth = 3,
586
+ variant = "card",
587
+ style,
588
+ ...rest
589
+ }) => /* @__PURE__ */ jsx8(
590
+ Section,
591
+ {
592
+ variant,
593
+ ...rest,
594
+ style: { borderTop: `${accentWidth}px solid ${accentColor}`, ...style }
595
+ }
596
+ );
597
+
598
+ // src/containers/FormHeader.tsx
599
+ import { mergeStyleSets as mergeStyleSets4 } from "@fluentui/react";
600
+ import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
601
+ var FormHeader = ({
602
+ title,
603
+ subtitle,
604
+ status,
605
+ titleColor,
606
+ subtitleColor,
607
+ persona,
608
+ actions,
609
+ className
610
+ }) => {
611
+ const styles = mergeStyleSets4({
612
+ root: {
613
+ display: "flex",
614
+ alignItems: "center",
615
+ justifyContent: "space-between",
616
+ gap: 12,
617
+ padding: "12px 24px",
618
+ borderBottom: `1px solid ${colors.borderNeutral}`,
619
+ background: colors.surface
620
+ },
621
+ main: { display: "flex", alignItems: "center", gap: 12, minWidth: 0 },
622
+ titleRow: { display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap" },
623
+ title: {
624
+ fontSize: typography.fontSize.lg,
625
+ fontWeight: typography.fontWeight.semibold,
626
+ fontFamily: typography.fontFamilySemibold,
627
+ color: titleColor || colors.textPrimary,
628
+ lineHeight: "1.3"
629
+ },
630
+ status: { fontSize: typography.fontSize.smPlus, color: colors.textSecondary },
631
+ subtitle: { fontSize: typography.fontSize.smPlus, color: subtitleColor || colors.textSecondary, marginTop: 2 }
632
+ });
633
+ return /* @__PURE__ */ jsxs4("div", { className: [styles.root, className].filter(Boolean).join(" "), children: [
634
+ /* @__PURE__ */ jsxs4("div", { className: styles.main, children: [
635
+ persona,
636
+ /* @__PURE__ */ jsxs4("div", { children: [
637
+ /* @__PURE__ */ jsxs4("div", { className: styles.titleRow, children: [
638
+ title != null && /* @__PURE__ */ jsx9("span", { className: styles.title, children: title }),
639
+ status != null && /* @__PURE__ */ jsx9("span", { className: styles.status, children: status })
640
+ ] }),
641
+ subtitle != null && /* @__PURE__ */ jsx9("div", { className: styles.subtitle, children: subtitle })
642
+ ] })
643
+ ] }),
644
+ actions != null && /* @__PURE__ */ jsx9("div", { children: actions })
645
+ ] });
646
+ };
647
+
648
+ // src/containers/PivotSurface.tsx
649
+ import { useState as useState4 } from "react";
650
+ import { Pivot, PivotItem } from "@fluentui/react";
651
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
652
+ var PivotSurface = ({
653
+ tabs,
654
+ selectedId,
655
+ defaultSelectedId,
656
+ onTabChange,
657
+ linkFormat = "links",
658
+ className
659
+ }) => {
660
+ const firstEnabledId = tabs.find((t) => !t.disabled)?.id ?? tabs[0]?.id;
661
+ const [internal, setInternal] = useState4(defaultSelectedId ?? firstEnabledId);
662
+ const selected = selectedId ?? internal;
663
+ const handleLinkClick = (item) => {
664
+ const id = item?.props.itemKey;
665
+ if (!id) return;
666
+ if (selectedId == null) setInternal(id);
667
+ onTabChange?.(id);
668
+ };
669
+ const active = tabs.find((t) => t.id === selected) ?? tabs[0];
670
+ return /* @__PURE__ */ jsxs5("div", { className, children: [
671
+ /* @__PURE__ */ jsx10(
672
+ Pivot,
673
+ {
674
+ selectedKey: selected,
675
+ onLinkClick: handleLinkClick,
676
+ linkFormat,
677
+ headersOnly: true,
678
+ children: tabs.map((t) => /* @__PURE__ */ jsx10(
679
+ PivotItem,
680
+ {
681
+ itemKey: t.id,
682
+ headerText: t.label,
683
+ itemIcon: t.iconName,
684
+ headerButtonProps: t.disabled ? { disabled: true } : void 0
685
+ },
686
+ t.id
687
+ ))
688
+ }
689
+ ),
690
+ /* @__PURE__ */ jsx10("div", { children: active?.content })
691
+ ] });
692
+ };
693
+
694
+ // src/containers/CommandSurface.tsx
695
+ import { CommandBar as CommandBar2 } from "@fluentui/react";
696
+ import { jsx as jsx11 } from "react/jsx-runtime";
697
+ var CommandSurface = ({ items, overflowItems, farItems, className }) => /* @__PURE__ */ jsx11("div", { className, children: /* @__PURE__ */ jsx11(CommandBar2, { items, overflowItems, farItems, styles: { root: { padding: 0 } } }) });
698
+
699
+ // src/containers/ActionBar.tsx
700
+ import { mergeStyles as mergeStyles4, DefaultButton, PrimaryButton, ActionButton } from "@fluentui/react";
701
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
702
+ function renderButton(b) {
703
+ const common = {
704
+ text: b.label,
705
+ disabled: b.disabled,
706
+ onClick: b.onClick,
707
+ iconProps: b.iconName ? { iconName: b.iconName } : void 0
708
+ };
709
+ if (b.appearance === "primary") return /* @__PURE__ */ jsx12(PrimaryButton, { ...common }, b.id);
710
+ if (b.appearance === "subtle") return /* @__PURE__ */ jsx12(ActionButton, { ...common }, b.id);
711
+ return /* @__PURE__ */ jsx12(DefaultButton, { ...common }, b.id);
712
+ }
713
+ var ActionBar = ({ buttons, alignment = "right", sticky = true, className }) => {
714
+ const justify = alignment === "left" ? "flex-start" : alignment === "center" ? "center" : "flex-end";
715
+ const visible = buttons.filter((b) => !b.hidden);
716
+ const left = visible.filter((b) => b.position === "left");
717
+ const right = visible.filter((b) => b.position !== "left");
718
+ const cls = mergeStyles4(
719
+ {
720
+ display: "flex",
721
+ alignItems: "center",
722
+ justifyContent: left.length ? "space-between" : justify,
723
+ gap: 8,
724
+ padding: "12px 24px",
725
+ borderTop: `1px solid ${colors.borderNeutral}`,
726
+ background: colors.surface,
727
+ ...sticky ? { position: "sticky", bottom: 0, zIndex: 1 } : {}
728
+ },
729
+ className
730
+ );
731
+ return /* @__PURE__ */ jsxs6("div", { className: cls, children: [
732
+ left.length > 0 && /* @__PURE__ */ jsx12("div", { style: { display: "flex", gap: 8 }, children: left.map(renderButton) }),
733
+ /* @__PURE__ */ jsx12("div", { style: { display: "flex", gap: 8, justifyContent: justify }, children: right.map(renderButton) })
734
+ ] });
735
+ };
736
+
737
+ // src/containers/Footer.tsx
738
+ import { mergeStyles as mergeStyles5 } from "@fluentui/react";
739
+ import { jsx as jsx13 } from "react/jsx-runtime";
740
+ var Footer = ({ sticky = true, align = "right", className, children }) => {
741
+ const justify = align === "left" ? "flex-start" : align === "center" ? "center" : "flex-end";
742
+ const cls = mergeStyles5(
743
+ {
744
+ display: "flex",
745
+ alignItems: "center",
746
+ justifyContent: justify,
747
+ gap: 8,
748
+ padding: "12px 24px",
749
+ borderTop: `1px solid ${colors.borderNeutral}`,
750
+ background: colors.surface,
751
+ ...sticky ? { position: "sticky", bottom: 0, zIndex: 1 } : {}
752
+ },
753
+ className
754
+ );
755
+ return /* @__PURE__ */ jsx13("div", { className: cls, children });
756
+ };
757
+
758
+ // src/overlays/FormDialogSurface.tsx
759
+ import { Modal, IconButton, mergeStyleSets as mergeStyleSets5 } from "@fluentui/react";
760
+ import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
761
+ var FormDialogSurface = ({
762
+ isOpen,
763
+ onDismiss,
764
+ title,
765
+ subheader,
766
+ size,
767
+ width,
768
+ height,
769
+ isBlocking = true,
770
+ recordCounter,
771
+ footer,
772
+ className,
773
+ children
774
+ }) => {
775
+ const widthCss = dialogWidthCss({ width, size });
776
+ const heightCss = dialogHeightCss(height);
777
+ const styles = mergeStyleSets5({
778
+ header: {
779
+ display: "flex",
780
+ alignItems: "center",
781
+ justifyContent: "space-between",
782
+ gap: 12,
783
+ padding: "12px 20px",
784
+ borderBottom: `1px solid ${colors.borderNeutral}`
785
+ },
786
+ headerLeft: { display: "flex", flexDirection: "column", minWidth: 0 },
787
+ title: {
788
+ fontSize: typography.fontSize.lg,
789
+ fontWeight: typography.fontWeight.semibold,
790
+ fontFamily: typography.fontFamilySemibold,
791
+ color: colors.textPrimary
792
+ },
793
+ subheader: { fontSize: typography.fontSize.smPlus, color: colors.textSecondary, marginTop: 2 },
794
+ headerRight: { display: "flex", alignItems: "center", gap: 8 },
795
+ counter: { fontSize: typography.fontSize.smPlus, color: colors.textSecondary },
796
+ content: { flex: 1, minHeight: 0, overflow: "auto", padding: 20 },
797
+ footer: { borderTop: `1px solid ${colors.borderNeutral}`, padding: "12px 20px" }
798
+ });
799
+ const showHeader = Boolean(title) || Boolean(recordCounter) || Boolean(onDismiss);
800
+ return /* @__PURE__ */ jsxs7(
801
+ Modal,
802
+ {
803
+ isOpen,
804
+ onDismiss,
805
+ isBlocking,
806
+ className,
807
+ styles: {
808
+ main: {
809
+ maxWidth: "95vw",
810
+ width: widthCss,
811
+ height: heightCss,
812
+ maxHeight: "95vh",
813
+ display: "flex",
814
+ flexDirection: "column"
815
+ },
816
+ scrollableContent: {
817
+ overflow: "hidden",
818
+ display: "flex",
819
+ flexDirection: "column",
820
+ flex: 1,
821
+ minHeight: 0
822
+ }
823
+ },
824
+ children: [
825
+ showHeader && /* @__PURE__ */ jsxs7("div", { className: styles.header, children: [
826
+ /* @__PURE__ */ jsxs7("div", { className: styles.headerLeft, children: [
827
+ title && /* @__PURE__ */ jsx14("span", { className: styles.title, children: title }),
828
+ subheader && /* @__PURE__ */ jsx14("span", { className: styles.subheader, children: subheader })
829
+ ] }),
830
+ /* @__PURE__ */ jsxs7("div", { className: styles.headerRight, children: [
831
+ recordCounter && /* @__PURE__ */ jsxs7("span", { className: styles.counter, children: [
832
+ recordCounter.current,
833
+ " of ",
834
+ recordCounter.total
835
+ ] }),
836
+ onDismiss && /* @__PURE__ */ jsx14(
837
+ IconButton,
838
+ {
839
+ iconProps: { iconName: "Cancel" },
840
+ ariaLabel: "Close",
841
+ onClick: onDismiss,
842
+ styles: { root: { color: colors.textSecondary } }
843
+ }
844
+ )
845
+ ] })
846
+ ] }),
847
+ /* @__PURE__ */ jsx14("div", { className: styles.content, children }),
848
+ footer && /* @__PURE__ */ jsx14("div", { className: styles.footer, children: footer })
849
+ ]
850
+ }
851
+ );
852
+ };
853
+
854
+ // src/overlays/ModalSurface.tsx
855
+ import { Modal as Modal2, mergeStyleSets as mergeStyleSets6 } from "@fluentui/react";
856
+ import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
857
+ var ModalSurface = ({
858
+ isOpen,
859
+ onDismiss,
860
+ title,
861
+ size,
862
+ width,
863
+ isBlocking = true,
864
+ autoSize,
865
+ footer,
866
+ className,
867
+ children
868
+ }) => {
869
+ const main = autoSize ? { width: "auto", minWidth: MODAL_AUTOSIZE_MIN_WIDTH, maxWidth: "100%" } : { maxWidth: "95vw", width: `${dialogWidthPx({ width, size })}px` };
870
+ const styles = mergeStyleSets6({
871
+ title: {
872
+ fontSize: typography.fontSize.lg,
873
+ fontWeight: typography.fontWeight.semibold,
874
+ fontFamily: typography.fontFamilySemibold,
875
+ color: colors.textPrimary,
876
+ padding: "16px 20px 0"
877
+ },
878
+ content: { padding: 20 },
879
+ footer: {
880
+ borderTop: `1px solid ${colors.borderNeutral}`,
881
+ padding: "12px 20px",
882
+ display: "flex",
883
+ justifyContent: "flex-end",
884
+ gap: 8
885
+ }
886
+ });
887
+ return /* @__PURE__ */ jsxs8(Modal2, { isOpen, onDismiss, isBlocking, className, styles: { main }, children: [
888
+ title && /* @__PURE__ */ jsx15("div", { className: styles.title, children: title }),
889
+ /* @__PURE__ */ jsx15("div", { className: styles.content, children }),
890
+ footer && /* @__PURE__ */ jsx15("div", { className: styles.footer, children: footer })
891
+ ] });
892
+ };
893
+
894
+ // src/overlays/PanelSurface.tsx
895
+ import { Panel, PanelType } from "@fluentui/react";
896
+ import { Fragment, jsx as jsx16 } from "react/jsx-runtime";
897
+ var PanelSurface = ({
898
+ isOpen,
899
+ onDismiss,
900
+ title,
901
+ panelType,
902
+ width,
903
+ hasCloseButton = true,
904
+ isLightDismiss = false,
905
+ isFooterAtBottom = true,
906
+ isNonModal = false,
907
+ footer,
908
+ children
909
+ }) => {
910
+ const kind = panelType ?? panelKindFromWidth(width);
911
+ const resolvedType = PanelType[kind];
912
+ const customWidth = kind === "custom" || kind === "customNear" ? `${panelCustomWidthPx(width)}px` : void 0;
913
+ return /* @__PURE__ */ jsx16(
914
+ Panel,
915
+ {
916
+ isOpen,
917
+ onDismiss,
918
+ headerText: title,
919
+ type: resolvedType,
920
+ customWidth,
921
+ hasCloseButton,
922
+ isLightDismiss,
923
+ isFooterAtBottom,
924
+ isBlocking: !isNonModal,
925
+ onRenderFooterContent: footer ? () => /* @__PURE__ */ jsx16(Fragment, { children: footer }) : void 0,
926
+ children: /* @__PURE__ */ jsx16("div", { style: { padding: "12px 0" }, children })
927
+ }
928
+ );
929
+ };
930
+
931
+ // src/overlays/CalloutSurface.tsx
932
+ import { Callout, DirectionalHint } from "@fluentui/react";
933
+ import { jsx as jsx17 } from "react/jsx-runtime";
934
+ var CalloutSurface = ({
935
+ target,
936
+ isOpen = true,
937
+ onDismiss,
938
+ width,
939
+ directionalHint = "bottomLeftEdge",
940
+ isBeakVisible = true,
941
+ beakWidth = 16,
942
+ gapSpace = 0,
943
+ className,
944
+ children
945
+ }) => {
946
+ if (!isOpen) return null;
947
+ return /* @__PURE__ */ jsx17(
948
+ Callout,
949
+ {
950
+ target: target ?? void 0,
951
+ onDismiss,
952
+ directionalHint: DirectionalHint[directionalHint],
953
+ isBeakVisible,
954
+ beakWidth,
955
+ gapSpace,
956
+ className,
957
+ styles: { root: { width: calloutWidthPx(width), padding: 16 } },
958
+ children
959
+ }
960
+ );
961
+ };
962
+
963
+ // src/overlays/TooltipSurface.tsx
964
+ import { TooltipHost, DirectionalHint as DirectionalHint2, TooltipDelay } from "@fluentui/react";
965
+ import { jsx as jsx18 } from "react/jsx-runtime";
966
+ var DELAY_MAP = {
967
+ zero: TooltipDelay.zero,
968
+ medium: TooltipDelay.medium,
969
+ long: TooltipDelay.long
970
+ };
971
+ var TooltipSurface = ({
972
+ content,
973
+ directionalHint = "bottomCenter",
974
+ delay = "medium",
975
+ children
976
+ }) => /* @__PURE__ */ jsx18(TooltipHost, { content, directionalHint: DirectionalHint2[directionalHint], delay: DELAY_MAP[delay], children });
977
+
978
+ // src/composites/MasterDetailSurface.tsx
979
+ import { mergeStyles as mergeStyles6 } from "@fluentui/react";
980
+ import { jsx as jsx19, jsxs as jsxs9 } from "react/jsx-runtime";
981
+ var MasterDetailSurface = ({
982
+ master,
983
+ detail,
984
+ detailFallback,
985
+ splitRatio = 0.38,
986
+ collapseBelow = "md",
987
+ className
988
+ }) => {
989
+ const masterFr = Math.min(90, Math.max(10, Math.round(splitRatio * 100)));
990
+ const detailFr = 100 - masterFr;
991
+ const collapsePx = BREAKPOINTS[collapseBelow];
992
+ const rootCls = mergeStyles6(
993
+ {
994
+ display: "grid",
995
+ gridTemplateColumns: `${masterFr}fr ${detailFr}fr`,
996
+ minHeight: 0,
997
+ selectors: { [`@media (max-width: ${collapsePx}px)`]: { gridTemplateColumns: "1fr" } }
998
+ },
999
+ className
1000
+ );
1001
+ const masterCls = mergeStyles6({
1002
+ minWidth: 0,
1003
+ overflow: "auto",
1004
+ borderRight: `1px solid ${colors.borderNeutral}`,
1005
+ selectors: {
1006
+ [`@media (max-width: ${collapsePx}px)`]: {
1007
+ borderRight: "none",
1008
+ borderBottom: `1px solid ${colors.borderNeutral}`
1009
+ }
1010
+ }
1011
+ });
1012
+ const detailCls = mergeStyles6({ minWidth: 0, overflow: "auto" });
1013
+ return /* @__PURE__ */ jsxs9("div", { className: rootCls, children: [
1014
+ /* @__PURE__ */ jsx19("div", { className: masterCls, children: master }),
1015
+ /* @__PURE__ */ jsx19("div", { className: detailCls, children: detail ?? detailFallback })
1016
+ ] });
1017
+ };
1018
+
1019
+ // src/composites/DashboardSurface.tsx
1020
+ import { mergeStyles as mergeStyles7 } from "@fluentui/react";
1021
+ import { jsx as jsx20 } from "react/jsx-runtime";
1022
+ var DashboardSurface = ({ tiles, columns = 2, gap = 16, className }) => {
1023
+ const cls = mergeStyles7(
1024
+ {
1025
+ display: "grid",
1026
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
1027
+ gap,
1028
+ alignItems: "start",
1029
+ selectors: {
1030
+ [`@media (max-width: ${CARD_REFLOW_WIDTH}px)`]: { gridTemplateColumns: "repeat(2, minmax(0, 1fr))" },
1031
+ [`@media (max-width: ${BREAKPOINTS.sm}px)`]: { gridTemplateColumns: "1fr" }
1032
+ }
1033
+ },
1034
+ className
1035
+ );
1036
+ return /* @__PURE__ */ jsx20("div", { className: cls, children: tiles.map((tile, i) => /* @__PURE__ */ jsx20("div", { children: tile }, `tile-${i}`)) });
1037
+ };
1038
+
1039
+ // src/adapters/fromFormDefinition.ts
1040
+ function fromFormDefinition(def) {
1041
+ const s = def.settings ?? {};
1042
+ switch (def.type) {
1043
+ case "dialog":
1044
+ return {
1045
+ kind: "dialog",
1046
+ widthPx: dialogWidthPx({ width: s.width }),
1047
+ heightCss: dialogHeightCss(s.height),
1048
+ title: def.name
1049
+ };
1050
+ case "panel": {
1051
+ const panelType = s.panelType ?? panelKindFromWidth(s.width);
1052
+ return {
1053
+ kind: "panel",
1054
+ panelType,
1055
+ customWidthPx: panelType === "custom" ? panelCustomWidthPx(s.width) : void 0,
1056
+ title: def.name
1057
+ };
1058
+ }
1059
+ case "callout":
1060
+ return { kind: "callout", widthPx: calloutWidthPx(s.width), title: def.name };
1061
+ case "main":
1062
+ default:
1063
+ return { kind: "main" };
1064
+ }
1065
+ }
1066
+ export {
1067
+ ActionBar,
1068
+ BREAKPOINTS,
1069
+ CALLOUT_DEFAULT_WIDTH,
1070
+ CARD_REFLOW_WIDTH,
1071
+ CalloutSurface,
1072
+ ColumnLayout,
1073
+ CommandSurface,
1074
+ DIALOG_SIZE_PX,
1075
+ DashboardSurface,
1076
+ FieldRow,
1077
+ Footer,
1078
+ FormDialogSurface,
1079
+ FormHeader,
1080
+ FormSurface,
1081
+ MODAL_AUTOSIZE_MIN_WIDTH,
1082
+ MasterDetailSurface,
1083
+ ModalSurface,
1084
+ PANEL_DEFAULT_WIDTH,
1085
+ PageSurface,
1086
+ PanelSurface,
1087
+ PivotSurface,
1088
+ Section,
1089
+ SectionCard,
1090
+ SectionHeader,
1091
+ TooltipSurface,
1092
+ TwoColumnLayout,
1093
+ calloutFormDefaults,
1094
+ calloutWidthPx,
1095
+ clampSpan,
1096
+ colors,
1097
+ createDynamicsTheme,
1098
+ densityScale,
1099
+ dialogHeightCss,
1100
+ dialogWidthCss,
1101
+ dialogWidthPx,
1102
+ fieldGridStyle,
1103
+ fieldRowStyle,
1104
+ fromFormDefinition,
1105
+ gridTemplateForColumns,
1106
+ layout,
1107
+ panelCustomWidthPx,
1108
+ panelKindFromWidth,
1109
+ radius,
1110
+ resolveBreakpoint,
1111
+ resolveResponsiveValue,
1112
+ scaleSpacing,
1113
+ sectionStackingStyle,
1114
+ spacing,
1115
+ typography,
1116
+ useContainerBreakpoint,
1117
+ useContainerSize,
1118
+ useContainerWidth,
1119
+ useMediaQuery,
1120
+ useResponsiveValue
1121
+ };
1122
+ //# sourceMappingURL=index.mjs.map