@dataverse-kit/surface-kit 0.2.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/dist/v9.mjs ADDED
@@ -0,0 +1,937 @@
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
+ import { colors, spacing, typography, radius, layout } from "@dataverse-kit/design-tokens";
124
+
125
+ // src/tokens/density.ts
126
+ var densityScale = {
127
+ comfortable: 1,
128
+ compact: 0.75
129
+ };
130
+ function scaleSpacing(value, density = "comfortable") {
131
+ return Math.round(value * densityScale[density]);
132
+ }
133
+
134
+ // src/hooks/useContainerSize.ts
135
+ import { useState, useEffect } from "react";
136
+ function useContainerSize(ref) {
137
+ const [size, setSize] = useState({ width: 0, height: 0, inlineSize: 0, blockSize: 0 });
138
+ useEffect(() => {
139
+ if (!ref.current || typeof ResizeObserver === "undefined") return;
140
+ const observer = new ResizeObserver((entries) => {
141
+ for (const entry of entries) {
142
+ const { width, height } = entry.contentRect;
143
+ const inlineSize = entry.borderBoxSize?.[0]?.inlineSize ?? width;
144
+ const blockSize = entry.borderBoxSize?.[0]?.blockSize ?? height;
145
+ setSize({ width, height, inlineSize, blockSize });
146
+ }
147
+ });
148
+ observer.observe(ref.current);
149
+ return () => observer.disconnect();
150
+ }, [ref]);
151
+ return size;
152
+ }
153
+
154
+ // src/hooks/useContainerWidth.ts
155
+ import { useRef } from "react";
156
+ function useContainerWidth() {
157
+ const ref = useRef(null);
158
+ const { width } = useContainerSize(ref);
159
+ return [ref, width];
160
+ }
161
+
162
+ // src/hooks/useContainerBreakpoint.ts
163
+ function useContainerBreakpoint() {
164
+ const [ref, width] = useContainerWidth();
165
+ return [ref, resolveBreakpoint(width), width];
166
+ }
167
+
168
+ // src/hooks/useResponsiveValue.ts
169
+ function useResponsiveValue(value) {
170
+ const [ref, breakpoint] = useContainerBreakpoint();
171
+ return [ref, resolveResponsiveValue(value, breakpoint)];
172
+ }
173
+
174
+ // src/hooks/useMediaQuery.ts
175
+ import { useState as useState2, useEffect as useEffect2 } from "react";
176
+ function useMediaQuery(query) {
177
+ const [matches, setMatches] = useState2(() => {
178
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
179
+ return window.matchMedia(query).matches;
180
+ });
181
+ useEffect2(() => {
182
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
183
+ const mql = window.matchMedia(query);
184
+ const onChange = () => setMatches(mql.matches);
185
+ onChange();
186
+ mql.addEventListener("change", onChange);
187
+ return () => mql.removeEventListener("change", onChange);
188
+ }, [query]);
189
+ return matches;
190
+ }
191
+
192
+ // src/hooks/useDensity.ts
193
+ import { createContext, useContext } from "react";
194
+ var DensityContext = createContext("comfortable");
195
+ var useDensity = () => useContext(DensityContext);
196
+
197
+ // src/v9/theme.ts
198
+ import {
199
+ createLightTheme,
200
+ createDarkTheme
201
+ } from "@fluentui/react-components";
202
+ var dynamicsBrandRamp = {
203
+ 10: "#061724",
204
+ 20: "#082338",
205
+ 30: "#0a2e4a",
206
+ 40: "#0c3b5e",
207
+ 50: "#0e4775",
208
+ 60: colors.brandDark,
209
+ // #005a9e — colorBrandBackgroundPressed
210
+ 70: colors.brandHover,
211
+ // #106ebe — colorBrandBackground(Hover/Selected)
212
+ 80: colors.brand,
213
+ // #0078d4 — colorBrandBackground + colorBrandForeground1 (the Dynamics blue)
214
+ 90: "#2886de",
215
+ 100: "#479ef5",
216
+ 110: "#62abf5",
217
+ 120: "#77b7f7",
218
+ 130: "#96c6fa",
219
+ 140: "#b4d6fa",
220
+ 150: "#cfe4fa",
221
+ 160: "#ebf3fc"
222
+ };
223
+ function createDynamicsV9Theme(brand = dynamicsBrandRamp) {
224
+ return createLightTheme(brand);
225
+ }
226
+ function createDynamicsV9DarkTheme(brand = dynamicsBrandRamp) {
227
+ return createDarkTheme(brand);
228
+ }
229
+ var dynamicsV9Theme = createDynamicsV9Theme();
230
+
231
+ // src/v9/containers/FormSurface.tsx
232
+ import { makeStyles, mergeClasses, tokens } from "@fluentui/react-components";
233
+ import { jsx } from "react/jsx-runtime";
234
+ var useStyles = makeStyles({
235
+ root: {
236
+ display: "grid",
237
+ alignItems: "start",
238
+ backgroundColor: tokens.colorNeutralBackground2
239
+ },
240
+ // Container forced a single column: reset every child's span the same way the @media
241
+ // fallback does, so a Section with `columnStart` doesn't escape the stack.
242
+ forceStackChildren: {
243
+ "& > *": { gridColumn: "1 / -1" }
244
+ },
245
+ // Viewport fallbacks (used only when containerWidth is unset) — one static slot per
246
+ // breakpoint so griffel gets literal media strings. The px come from the shared BREAKPOINTS.
247
+ stackSm: { [`@media (max-width: ${BREAKPOINTS.sm}px)`]: { "& > *": { gridColumn: "1 / -1" } } },
248
+ stackMd: { [`@media (max-width: ${BREAKPOINTS.md}px)`]: { "& > *": { gridColumn: "1 / -1" } } },
249
+ stackLg: { [`@media (max-width: ${BREAKPOINTS.lg}px)`]: { "& > *": { gridColumn: "1 / -1" } } },
250
+ stackXl: { [`@media (max-width: ${BREAKPOINTS.xl}px)`]: { "& > *": { gridColumn: "1 / -1" } } }
251
+ });
252
+ var VIEWPORT_STACK = {
253
+ xs: null,
254
+ // xs = 0px, never stacks
255
+ sm: "stackSm",
256
+ md: "stackMd",
257
+ lg: "stackLg",
258
+ xl: "stackXl"
259
+ };
260
+ var FormSurface = ({
261
+ columns = 12,
262
+ gap = 20,
263
+ padding = 20,
264
+ density = "comfortable",
265
+ background,
266
+ containerWidth,
267
+ collapseBelow = "sm",
268
+ maxWidth,
269
+ className,
270
+ style,
271
+ children
272
+ }) => {
273
+ const styles = useStyles();
274
+ const collapsePx = BREAKPOINTS[collapseBelow];
275
+ const forceStack = containerWidth != null && containerWidth <= collapsePx;
276
+ const scaledGap = scaleSpacing(gap, density);
277
+ const scaledPadding = scaleSpacing(padding, density);
278
+ const viewportStackSlot = containerWidth == null ? VIEWPORT_STACK[collapseBelow] : null;
279
+ const cls = mergeClasses(
280
+ styles.root,
281
+ forceStack && styles.forceStackChildren,
282
+ viewportStackSlot ? styles[viewportStackSlot] : void 0,
283
+ className
284
+ );
285
+ const inlineStyle = {
286
+ gridTemplateColumns: forceStack ? "1fr" : `repeat(${columns}, 1fr)`,
287
+ gap: scaledGap,
288
+ padding: scaledPadding,
289
+ ...background != null ? { background } : {},
290
+ ...maxWidth != null ? { maxWidth, marginLeft: "auto", marginRight: "auto" } : {},
291
+ ...style
292
+ };
293
+ return /* @__PURE__ */ jsx(DensityContext.Provider, { value: density, children: /* @__PURE__ */ jsx("div", { className: cls, style: inlineStyle, children }) });
294
+ };
295
+
296
+ // src/v9/containers/PageSurface.tsx
297
+ import { jsx as jsx2 } from "react/jsx-runtime";
298
+ var PageSurface = ({ centered, maxWidth, ...rest }) => /* @__PURE__ */ jsx2(FormSurface, { maxWidth: maxWidth ?? (centered ? 1200 : void 0), ...rest });
299
+
300
+ // src/v9/containers/Section.tsx
301
+ import { useState as useState3 } from "react";
302
+ import { makeStyles as makeStyles2, mergeClasses as mergeClasses2, tokens as tokens3 } from "@fluentui/react-components";
303
+
304
+ // src/v9/containers/SectionHeader.tsx
305
+ import {
306
+ Button,
307
+ Text,
308
+ Toolbar,
309
+ ToolbarButton,
310
+ tokens as tokens2
311
+ } from "@fluentui/react-components";
312
+ import { ChevronDown20Regular, ChevronRight20Regular } from "@fluentui/react-icons";
313
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
314
+ var SectionHeader = ({
315
+ title,
316
+ statusText,
317
+ showCommandBar,
318
+ commandItems = [],
319
+ actions,
320
+ collapsible,
321
+ collapsed,
322
+ onToggle
323
+ }) => {
324
+ const rootStyle = {
325
+ display: "flex",
326
+ justifyContent: "space-between",
327
+ alignItems: "center",
328
+ flexWrap: "nowrap",
329
+ borderBottom: `1px solid ${tokens2.colorNeutralStroke2}`,
330
+ marginBottom: 16,
331
+ minHeight: 48,
332
+ width: "100%"
333
+ };
334
+ const leftStyle = { display: "flex", alignItems: "center", gap: 8, minWidth: 0 };
335
+ const statusStyle = {
336
+ marginLeft: 8,
337
+ fontSize: tokens2.fontSizeBase200,
338
+ color: tokens2.colorNeutralForeground3
339
+ };
340
+ return /* @__PURE__ */ jsxs("div", { style: rootStyle, children: [
341
+ /* @__PURE__ */ jsxs("div", { style: leftStyle, children: [
342
+ collapsible && /* @__PURE__ */ jsx3(
343
+ Button,
344
+ {
345
+ appearance: "subtle",
346
+ size: "small",
347
+ icon: collapsed ? /* @__PURE__ */ jsx3(ChevronRight20Regular, {}) : /* @__PURE__ */ jsx3(ChevronDown20Regular, {}),
348
+ onClick: onToggle,
349
+ "aria-label": collapsed ? "Expand section" : "Collapse section",
350
+ "aria-expanded": !collapsed
351
+ }
352
+ ),
353
+ title && /* @__PURE__ */ jsx3(Text, { weight: "semibold", size: 300, children: title }),
354
+ statusText && /* @__PURE__ */ jsx3("span", { style: statusStyle, children: statusText })
355
+ ] }),
356
+ actions ? /* @__PURE__ */ jsx3("div", { children: actions }) : showCommandBar && commandItems.length > 0 ? /* @__PURE__ */ jsx3(Toolbar, { "aria-label": "Section commands", size: "small", children: commandItems.map((item) => /* @__PURE__ */ jsx3(
357
+ ToolbarButton,
358
+ {
359
+ icon: item.icon,
360
+ appearance: item.appearance === "primary" ? "primary" : "subtle",
361
+ disabled: item.disabled,
362
+ onClick: item.onClick,
363
+ children: item.label
364
+ },
365
+ item.id
366
+ )) }) : null
367
+ ] });
368
+ };
369
+
370
+ // src/v9/containers/Section.tsx
371
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
372
+ var useStyles2 = makeStyles2({
373
+ // Phone-stack the internal field grid below `sm` (griffel — inline can't do @media).
374
+ bodyStack: {
375
+ [`@media (max-width: ${BREAKPOINTS.sm}px)`]: { "& > *": { gridColumn: "1 / -1" } }
376
+ }
377
+ });
378
+ var Section = ({
379
+ title,
380
+ columnSpan = 12,
381
+ columnStart,
382
+ rowPosition,
383
+ internalColumns = 12,
384
+ showCommandBar,
385
+ commandItems,
386
+ statusText,
387
+ notification,
388
+ collapsible,
389
+ defaultCollapsed = false,
390
+ collapsed: controlledCollapsed,
391
+ onCollapsedChange,
392
+ variant = "card",
393
+ minHeight = 80,
394
+ density: densityProp,
395
+ className,
396
+ style,
397
+ children
398
+ }) => {
399
+ const styles = useStyles2();
400
+ const [internalCollapsed, setInternalCollapsed] = useState3(defaultCollapsed);
401
+ const collapsed = controlledCollapsed ?? internalCollapsed;
402
+ const toggle = () => {
403
+ const next = !collapsed;
404
+ if (controlledCollapsed == null) setInternalCollapsed(next);
405
+ onCollapsedChange?.(next);
406
+ };
407
+ const ctxDensity = useDensity();
408
+ const density = densityProp ?? ctxDensity;
409
+ const cardPad = `${scaleSpacing(3, density)}px ${scaleSpacing(20, density)}px ${scaleSpacing(20, density)}px ${scaleSpacing(20, density)}px`;
410
+ const bodyGap = scaleSpacing(7, density);
411
+ const gridColumn = columnStart ? `${columnStart} / span ${columnSpan}` : `span ${columnSpan}`;
412
+ const rootStyle = {
413
+ gridColumn,
414
+ ...rowPosition ? { gridRowStart: rowPosition } : {},
415
+ ...style
416
+ };
417
+ if (variant === "placeholder") {
418
+ const placeholderStyle = {
419
+ ...rootStyle,
420
+ border: `1px dashed ${tokens3.colorNeutralStroke2}`,
421
+ borderRadius: tokens3.borderRadiusMedium,
422
+ minHeight,
423
+ width: "100%",
424
+ background: "transparent"
425
+ };
426
+ return /* @__PURE__ */ jsx4("div", { className, style: placeholderStyle });
427
+ }
428
+ const chrome = variant === "flat" ? {
429
+ backgroundColor: tokens3.colorNeutralBackground1,
430
+ borderRadius: tokens3.borderRadiusMedium,
431
+ padding: cardPad,
432
+ minWidth: 300,
433
+ width: "100%",
434
+ overflow: "hidden"
435
+ } : {
436
+ backgroundColor: tokens3.colorNeutralBackground1,
437
+ border: `1px solid ${tokens3.colorNeutralStroke2}`,
438
+ borderRadius: tokens3.borderRadiusMedium,
439
+ boxShadow: tokens3.shadow4,
440
+ padding: cardPad,
441
+ minWidth: 300,
442
+ width: "100%",
443
+ overflow: "hidden"
444
+ };
445
+ const showHeader = Boolean(title) || showCommandBar || collapsible;
446
+ const bodyStyle = {
447
+ display: "grid",
448
+ gridTemplateColumns: `repeat(${internalColumns}, 1fr)`,
449
+ gap: bodyGap,
450
+ alignItems: "start"
451
+ };
452
+ return /* @__PURE__ */ jsxs2("div", { className, style: { ...chrome, ...rootStyle }, children: [
453
+ notification && /* @__PURE__ */ jsx4("div", { style: { marginBottom: scaleSpacing(8, density) }, children: notification }),
454
+ showHeader && /* @__PURE__ */ jsx4(
455
+ SectionHeader,
456
+ {
457
+ title,
458
+ statusText,
459
+ showCommandBar,
460
+ commandItems,
461
+ collapsible,
462
+ collapsed,
463
+ onToggle: toggle
464
+ }
465
+ ),
466
+ !collapsed && /* @__PURE__ */ jsx4("div", { className: mergeClasses2(styles.bodyStack), style: bodyStyle, children })
467
+ ] });
468
+ };
469
+
470
+ // src/v9/containers/SectionCard.tsx
471
+ import { tokens as tokens4 } from "@fluentui/react-components";
472
+ import { jsx as jsx5 } from "react/jsx-runtime";
473
+ var SectionCard = ({
474
+ accentColor = tokens4.colorBrandStroke1,
475
+ accentWidth = 3,
476
+ variant = "card",
477
+ style,
478
+ ...rest
479
+ }) => /* @__PURE__ */ jsx5(
480
+ Section,
481
+ {
482
+ variant,
483
+ ...rest,
484
+ style: { borderTop: `${accentWidth}px solid ${accentColor}`, ...style }
485
+ }
486
+ );
487
+
488
+ // src/v9/containers/FormHeader.tsx
489
+ import { Text as Text2, tokens as tokens5 } from "@fluentui/react-components";
490
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
491
+ var FormHeader = ({
492
+ title,
493
+ subtitle,
494
+ status,
495
+ titleColor,
496
+ subtitleColor,
497
+ persona,
498
+ actions,
499
+ className
500
+ }) => {
501
+ const rootStyle = {
502
+ display: "flex",
503
+ alignItems: "center",
504
+ justifyContent: "space-between",
505
+ gap: 12,
506
+ padding: "12px 24px",
507
+ borderBottom: `1px solid ${tokens5.colorNeutralStroke2}`,
508
+ backgroundColor: tokens5.colorNeutralBackground1
509
+ };
510
+ const mainStyle = { display: "flex", alignItems: "center", gap: 12, minWidth: 0 };
511
+ const titleRowStyle = { display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap" };
512
+ const statusStyle = { fontSize: tokens5.fontSizeBase200, color: tokens5.colorNeutralForeground3 };
513
+ const subtitleStyle = {
514
+ fontSize: tokens5.fontSizeBase200,
515
+ color: subtitleColor || tokens5.colorNeutralForeground3,
516
+ marginTop: 2
517
+ };
518
+ return /* @__PURE__ */ jsxs3("div", { className, style: rootStyle, children: [
519
+ /* @__PURE__ */ jsxs3("div", { style: mainStyle, children: [
520
+ persona,
521
+ /* @__PURE__ */ jsxs3("div", { children: [
522
+ /* @__PURE__ */ jsxs3("div", { style: titleRowStyle, children: [
523
+ title != null && /* @__PURE__ */ jsx6(Text2, { weight: "semibold", size: 400, style: titleColor ? { color: titleColor } : void 0, children: title }),
524
+ status != null && /* @__PURE__ */ jsx6("span", { style: statusStyle, children: status })
525
+ ] }),
526
+ subtitle != null && /* @__PURE__ */ jsx6("div", { style: subtitleStyle, children: subtitle })
527
+ ] })
528
+ ] }),
529
+ actions != null && /* @__PURE__ */ jsx6("div", { children: actions })
530
+ ] });
531
+ };
532
+
533
+ // src/v9/containers/PivotSurface.tsx
534
+ import { useState as useState4 } from "react";
535
+ import { TabList, Tab } from "@fluentui/react-components";
536
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
537
+ var PivotSurface = ({
538
+ tabs,
539
+ selectedId,
540
+ defaultSelectedId,
541
+ onTabChange,
542
+ appearance = "transparent",
543
+ size = "medium",
544
+ className
545
+ }) => {
546
+ const firstEnabledId = tabs.find((t) => !t.disabled)?.id ?? tabs[0]?.id;
547
+ const [internal, setInternal] = useState4(defaultSelectedId ?? firstEnabledId);
548
+ const selected = selectedId ?? internal;
549
+ const handleSelect = (_e, data) => {
550
+ const id = String(data.value);
551
+ if (selectedId == null) setInternal(id);
552
+ onTabChange?.(id);
553
+ };
554
+ const active = tabs.find((t) => t.id === selected) ?? tabs[0];
555
+ return /* @__PURE__ */ jsxs4("div", { className, children: [
556
+ /* @__PURE__ */ jsx7(TabList, { selectedValue: selected, onTabSelect: handleSelect, appearance, size, children: tabs.map((t) => /* @__PURE__ */ jsx7(Tab, { value: t.id, icon: t.icon, disabled: t.disabled, children: t.label }, t.id)) }),
557
+ /* @__PURE__ */ jsx7("div", { children: active?.content })
558
+ ] });
559
+ };
560
+
561
+ // src/v9/containers/CommandSurface.tsx
562
+ import {
563
+ Toolbar as Toolbar2,
564
+ ToolbarButton as ToolbarButton2,
565
+ Menu,
566
+ MenuTrigger,
567
+ MenuPopover,
568
+ MenuList,
569
+ MenuItem
570
+ } from "@fluentui/react-components";
571
+ import { MoreHorizontal20Regular } from "@fluentui/react-icons";
572
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
573
+ function renderToolbarButton(item) {
574
+ return /* @__PURE__ */ jsx8(
575
+ ToolbarButton2,
576
+ {
577
+ icon: item.icon,
578
+ appearance: item.appearance === "primary" ? "primary" : "subtle",
579
+ disabled: item.disabled,
580
+ onClick: item.onClick,
581
+ children: item.label
582
+ },
583
+ item.id
584
+ );
585
+ }
586
+ var CommandSurface = ({
587
+ items,
588
+ overflowItems,
589
+ farItems,
590
+ size = "small",
591
+ className
592
+ }) => {
593
+ const hasFar = Boolean(farItems && farItems.length > 0);
594
+ const hasOverflow = Boolean(overflowItems && overflowItems.length > 0);
595
+ const hasPrimary = items.length > 0 || hasOverflow;
596
+ return /* @__PURE__ */ jsxs5("div", { className, style: { display: "flex", alignItems: "center", gap: 8 }, children: [
597
+ hasPrimary && /* @__PURE__ */ jsxs5(Toolbar2, { "aria-label": "Commands", size, children: [
598
+ items.map(renderToolbarButton),
599
+ hasOverflow && /* @__PURE__ */ jsxs5(Menu, { children: [
600
+ /* @__PURE__ */ jsx8(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx8(ToolbarButton2, { "aria-label": "More commands", icon: /* @__PURE__ */ jsx8(MoreHorizontal20Regular, {}) }) }),
601
+ /* @__PURE__ */ jsx8(MenuPopover, { children: /* @__PURE__ */ jsx8(MenuList, { children: overflowItems.map((item) => /* @__PURE__ */ jsx8(MenuItem, { icon: item.icon, disabled: item.disabled, onClick: item.onClick, children: item.label }, item.id)) }) })
602
+ ] })
603
+ ] }),
604
+ hasFar && // marginLeft:auto keeps far items right-aligned whether or not a primary toolbar renders.
605
+ /* @__PURE__ */ jsx8(Toolbar2, { "aria-label": "Secondary commands", size, style: { marginLeft: "auto" }, children: farItems.map(renderToolbarButton) })
606
+ ] });
607
+ };
608
+
609
+ // src/v9/containers/ActionBar.tsx
610
+ import { Button as Button2, tokens as tokens6 } from "@fluentui/react-components";
611
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
612
+ function renderButton(b) {
613
+ const appearance = b.appearance === "primary" ? "primary" : b.appearance === "subtle" ? "subtle" : "secondary";
614
+ return /* @__PURE__ */ jsx9(Button2, { appearance, icon: b.icon, disabled: b.disabled, onClick: b.onClick, children: b.label }, b.id);
615
+ }
616
+ var ActionBar = ({ buttons, alignment = "right", sticky = true, className }) => {
617
+ const justify = alignment === "left" ? "flex-start" : alignment === "center" ? "center" : "flex-end";
618
+ const visible = buttons.filter((b) => !b.hidden);
619
+ const left = visible.filter((b) => b.position === "left");
620
+ const right = visible.filter((b) => b.position !== "left");
621
+ const rootStyle = {
622
+ display: "flex",
623
+ alignItems: "center",
624
+ justifyContent: left.length ? "space-between" : justify,
625
+ gap: 8,
626
+ padding: "12px 24px",
627
+ borderTop: `1px solid ${tokens6.colorNeutralStroke2}`,
628
+ backgroundColor: tokens6.colorNeutralBackground1,
629
+ ...sticky ? { position: "sticky", bottom: 0, zIndex: 1 } : {}
630
+ };
631
+ return /* @__PURE__ */ jsxs6("div", { className, style: rootStyle, children: [
632
+ left.length > 0 && /* @__PURE__ */ jsx9("div", { style: { display: "flex", gap: 8 }, children: left.map(renderButton) }),
633
+ /* @__PURE__ */ jsx9("div", { style: { display: "flex", gap: 8, justifyContent: justify }, children: right.map(renderButton) })
634
+ ] });
635
+ };
636
+
637
+ // src/v9/containers/Footer.tsx
638
+ import { tokens as tokens7 } from "@fluentui/react-components";
639
+ import { jsx as jsx10 } from "react/jsx-runtime";
640
+ var Footer = ({ sticky = true, align = "right", className, children }) => {
641
+ const justify = align === "left" ? "flex-start" : align === "center" ? "center" : "flex-end";
642
+ const rootStyle = {
643
+ display: "flex",
644
+ alignItems: "center",
645
+ justifyContent: justify,
646
+ gap: 8,
647
+ padding: "12px 24px",
648
+ borderTop: `1px solid ${tokens7.colorNeutralStroke2}`,
649
+ backgroundColor: tokens7.colorNeutralBackground1,
650
+ ...sticky ? { position: "sticky", bottom: 0, zIndex: 1 } : {}
651
+ };
652
+ return /* @__PURE__ */ jsx10("div", { className, style: rootStyle, children });
653
+ };
654
+
655
+ // src/v9/overlays/FormDialogSurface.tsx
656
+ import { Dialog, DialogSurface, Button as Button3, Text as Text3, tokens as tokens8 } from "@fluentui/react-components";
657
+ import { Dismiss20Regular } from "@fluentui/react-icons";
658
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
659
+ var FormDialogSurface = ({
660
+ isOpen,
661
+ onDismiss,
662
+ title,
663
+ subheader,
664
+ size,
665
+ width,
666
+ height,
667
+ isBlocking = true,
668
+ recordCounter,
669
+ footer,
670
+ className,
671
+ children
672
+ }) => {
673
+ const widthCss = dialogWidthCss({ width, size });
674
+ const heightCss = dialogHeightCss(height);
675
+ const showHeader = Boolean(title) || Boolean(recordCounter) || Boolean(onDismiss);
676
+ const headerStyle = {
677
+ display: "flex",
678
+ alignItems: "center",
679
+ justifyContent: "space-between",
680
+ gap: 12,
681
+ padding: "12px 20px",
682
+ borderBottom: `1px solid ${tokens8.colorNeutralStroke2}`
683
+ };
684
+ const counterStyle = { fontSize: tokens8.fontSizeBase200, color: tokens8.colorNeutralForeground3 };
685
+ return /* @__PURE__ */ jsx11(
686
+ Dialog,
687
+ {
688
+ open: isOpen,
689
+ modalType: isBlocking ? "modal" : "non-modal",
690
+ onOpenChange: (_e, data) => {
691
+ if (!data.open) onDismiss?.();
692
+ },
693
+ children: /* @__PURE__ */ jsxs7(
694
+ DialogSurface,
695
+ {
696
+ className,
697
+ style: {
698
+ maxWidth: "95vw",
699
+ width: widthCss,
700
+ height: heightCss,
701
+ maxHeight: "95vh",
702
+ display: "flex",
703
+ flexDirection: "column",
704
+ padding: 0
705
+ },
706
+ children: [
707
+ showHeader && /* @__PURE__ */ jsxs7("div", { style: headerStyle, children: [
708
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
709
+ title && /* @__PURE__ */ jsx11(Text3, { weight: "semibold", size: 400, children: title }),
710
+ subheader && /* @__PURE__ */ jsx11("span", { style: { fontSize: tokens8.fontSizeBase200, color: tokens8.colorNeutralForeground3, marginTop: 2 }, children: subheader })
711
+ ] }),
712
+ /* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
713
+ recordCounter && /* @__PURE__ */ jsxs7("span", { style: counterStyle, children: [
714
+ recordCounter.current,
715
+ " of ",
716
+ recordCounter.total
717
+ ] }),
718
+ onDismiss && /* @__PURE__ */ jsx11(Button3, { appearance: "subtle", "aria-label": "Close", icon: /* @__PURE__ */ jsx11(Dismiss20Regular, {}), onClick: onDismiss })
719
+ ] })
720
+ ] }),
721
+ /* @__PURE__ */ jsx11("div", { style: { flex: 1, minHeight: 0, overflow: "auto", padding: 20 }, children }),
722
+ footer && /* @__PURE__ */ jsx11("div", { style: { borderTop: `1px solid ${tokens8.colorNeutralStroke2}`, padding: "12px 20px" }, children: footer })
723
+ ]
724
+ }
725
+ )
726
+ }
727
+ );
728
+ };
729
+
730
+ // src/v9/overlays/ModalSurface.tsx
731
+ import { Dialog as Dialog2, DialogSurface as DialogSurface2, Text as Text4, tokens as tokens9 } from "@fluentui/react-components";
732
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
733
+ var ModalSurface = ({
734
+ isOpen,
735
+ onDismiss,
736
+ title,
737
+ size,
738
+ width,
739
+ isBlocking = true,
740
+ autoSize,
741
+ footer,
742
+ className,
743
+ children
744
+ }) => {
745
+ const sizing = autoSize ? { width: "auto", minWidth: MODAL_AUTOSIZE_MIN_WIDTH, maxWidth: "100%" } : { maxWidth: "95vw", width: `${dialogWidthPx({ width, size })}px` };
746
+ return /* @__PURE__ */ jsx12(
747
+ Dialog2,
748
+ {
749
+ open: isOpen,
750
+ modalType: isBlocking ? "modal" : "non-modal",
751
+ onOpenChange: (_e, data) => {
752
+ if (!data.open) onDismiss?.();
753
+ },
754
+ children: /* @__PURE__ */ jsxs8(DialogSurface2, { className, style: { ...sizing, padding: 0 }, children: [
755
+ title && /* @__PURE__ */ jsx12("div", { style: { padding: "16px 20px 0" }, children: /* @__PURE__ */ jsx12(Text4, { weight: "semibold", size: 500, children: title }) }),
756
+ /* @__PURE__ */ jsx12("div", { style: { padding: 20 }, children }),
757
+ footer && /* @__PURE__ */ jsx12(
758
+ "div",
759
+ {
760
+ style: {
761
+ borderTop: `1px solid ${tokens9.colorNeutralStroke2}`,
762
+ padding: "12px 20px",
763
+ display: "flex",
764
+ justifyContent: "flex-end",
765
+ gap: 8
766
+ },
767
+ children: footer
768
+ }
769
+ )
770
+ ] })
771
+ }
772
+ );
773
+ };
774
+
775
+ // src/v9/overlays/PanelSurface.tsx
776
+ import {
777
+ OverlayDrawer,
778
+ DrawerHeader,
779
+ DrawerHeaderTitle,
780
+ DrawerBody,
781
+ Button as Button4
782
+ } from "@fluentui/react-components";
783
+ import { Dismiss24Regular } from "@fluentui/react-icons";
784
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
785
+ var PanelSurface = ({
786
+ isOpen,
787
+ onDismiss,
788
+ title,
789
+ drawerSize,
790
+ width,
791
+ hasCloseButton = true,
792
+ isLightDismiss = true,
793
+ isNonModal = false,
794
+ footer,
795
+ children
796
+ }) => {
797
+ const size = drawerSize ?? (width != null && width > 500 ? "medium" : "small");
798
+ const customWidthStyle = width != null ? { width: `${width}px` } : void 0;
799
+ return /* @__PURE__ */ jsxs9(
800
+ OverlayDrawer,
801
+ {
802
+ open: isOpen,
803
+ position: "end",
804
+ size,
805
+ modalType: isNonModal ? "non-modal" : "modal",
806
+ style: customWidthStyle,
807
+ onOpenChange: (_e, data) => {
808
+ if (data.open) return;
809
+ if (data.type === "backdropClick" && !isLightDismiss) return;
810
+ onDismiss?.();
811
+ },
812
+ children: [
813
+ /* @__PURE__ */ jsx13(DrawerHeader, { children: /* @__PURE__ */ jsx13(
814
+ DrawerHeaderTitle,
815
+ {
816
+ action: hasCloseButton && onDismiss ? /* @__PURE__ */ jsx13(Button4, { appearance: "subtle", "aria-label": "Close", icon: /* @__PURE__ */ jsx13(Dismiss24Regular, {}), onClick: onDismiss }) : void 0,
817
+ children: title
818
+ }
819
+ ) }),
820
+ /* @__PURE__ */ jsxs9(DrawerBody, { children: [
821
+ /* @__PURE__ */ jsx13("div", { style: { padding: "12px 0" }, children }),
822
+ footer && /* @__PURE__ */ jsx13("div", { style: { marginTop: "auto", paddingTop: 12 }, children: footer })
823
+ ] })
824
+ ]
825
+ }
826
+ );
827
+ };
828
+
829
+ // src/v9/overlays/CalloutSurface.tsx
830
+ import { Popover, PopoverSurface } from "@fluentui/react-components";
831
+ import { jsx as jsx14 } from "react/jsx-runtime";
832
+ function resolveTarget(target) {
833
+ if (!target) return null;
834
+ if (typeof target === "string") return document.querySelector(target);
835
+ if ("current" in target) return target.current ?? null;
836
+ return target;
837
+ }
838
+ var CalloutSurface = ({
839
+ target,
840
+ isOpen = true,
841
+ onDismiss,
842
+ width,
843
+ position = "below",
844
+ align = "start",
845
+ isBeakVisible = true,
846
+ className,
847
+ children
848
+ }) => {
849
+ if (!isOpen) return null;
850
+ const el = resolveTarget(target);
851
+ const positioning = { position, align, ...el ? { target: el } : {} };
852
+ return /* @__PURE__ */ jsx14(
853
+ Popover,
854
+ {
855
+ open: true,
856
+ trapFocus: false,
857
+ withArrow: isBeakVisible,
858
+ positioning,
859
+ onOpenChange: (_e, data) => {
860
+ if (!data.open) onDismiss?.();
861
+ },
862
+ children: /* @__PURE__ */ jsx14(PopoverSurface, { className, style: { width: calloutWidthPx(width), padding: 16 }, children })
863
+ }
864
+ );
865
+ };
866
+
867
+ // src/v9/overlays/TooltipSurface.tsx
868
+ import { Tooltip } from "@fluentui/react-components";
869
+ import { jsx as jsx15 } from "react/jsx-runtime";
870
+ var DELAY_MS = {
871
+ zero: 0,
872
+ medium: 250,
873
+ long: 500
874
+ };
875
+ var TooltipSurface = ({
876
+ content,
877
+ position = "above",
878
+ delay = "medium",
879
+ relationship = "label",
880
+ children
881
+ }) => /* @__PURE__ */ jsx15(Tooltip, { content, relationship, positioning: position, showDelay: DELAY_MS[delay], children });
882
+ export {
883
+ ActionBar,
884
+ BREAKPOINTS,
885
+ CALLOUT_DEFAULT_WIDTH,
886
+ CARD_REFLOW_WIDTH,
887
+ CalloutSurface,
888
+ CommandSurface,
889
+ DIALOG_SIZE_PX,
890
+ DensityContext,
891
+ Footer,
892
+ FormDialogSurface,
893
+ FormHeader,
894
+ FormSurface,
895
+ MODAL_AUTOSIZE_MIN_WIDTH,
896
+ ModalSurface,
897
+ PANEL_DEFAULT_WIDTH,
898
+ PageSurface,
899
+ PanelSurface,
900
+ PivotSurface,
901
+ Section,
902
+ SectionCard,
903
+ SectionHeader,
904
+ TooltipSurface,
905
+ calloutFormDefaults,
906
+ calloutWidthPx,
907
+ clampSpan,
908
+ colors,
909
+ createDynamicsV9DarkTheme,
910
+ createDynamicsV9Theme,
911
+ densityScale,
912
+ dialogHeightCss,
913
+ dialogWidthCss,
914
+ dialogWidthPx,
915
+ dynamicsBrandRamp,
916
+ dynamicsV9Theme,
917
+ fieldGridStyle,
918
+ fieldRowStyle,
919
+ gridTemplateForColumns,
920
+ layout,
921
+ panelCustomWidthPx,
922
+ panelKindFromWidth,
923
+ radius,
924
+ resolveBreakpoint,
925
+ resolveResponsiveValue,
926
+ scaleSpacing,
927
+ sectionStackingStyle,
928
+ spacing,
929
+ typography,
930
+ useContainerBreakpoint,
931
+ useContainerSize,
932
+ useContainerWidth,
933
+ useDensity,
934
+ useMediaQuery,
935
+ useResponsiveValue
936
+ };
937
+ //# sourceMappingURL=v9.mjs.map