@phis/ui 0.4.3 → 0.4.4
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/components/controls/phi-anchor-nav-control.js +4 -1
- package/components/controls/phi-background-control.js +21 -6
- package/components/controls/phi-bullet-list-control.d.ts +7 -2
- package/components/controls/phi-bullet-list-control.js +4 -3
- package/components/controls/phi-color-control.js +4 -2
- package/components/controls/phi-drawer-control-adapter.js +3 -2
- package/components/controls/phi-inline-text-editor.js +7 -1
- package/components/controls/phi-length-control.js +1 -1
- package/components/controls/phi-ribbon-control.d.ts +13 -4
- package/components/controls/phi-ribbon-control.js +10 -6
- package/components/controls/phi-ribbon-wrap-control.d.ts +26 -0
- package/components/controls/phi-ribbon-wrap-control.js +25 -0
- package/components/controls/phi-select-control.js +5 -2
- package/components/controls/phi-series-chart-control.d.ts +48 -0
- package/components/controls/phi-series-chart-control.js +82 -0
- package/components/controls/phi-text-control.js +57 -3
- package/components/controls/phi-tree-control.js +1 -1
- package/components/controls/phi-truncated-hover-text.d.ts +15 -0
- package/components/controls/phi-truncated-hover-text.js +25 -0
- package/components/forms/link-target-form-control.d.ts +1 -1
- package/components/forms/link-target-form-control.js +2 -45
- package/components/forms/phi-form-widget-frame.js +4 -2
- package/components/layouts/clients/phi-three-column-layout-client.js +10 -1
- package/components/navigation/phi-link.js +6 -1
- package/components/regions/clients/cms-region-container-client.js +6 -1
- package/components/regions/phi-cms-region-container.js +3 -0
- package/components/regions/phi-cms-region-shell.d.ts +12 -1
- package/components/regions/phi-cms-region-shell.js +81 -19
- package/components/root/phi-config-provider.d.ts +13 -0
- package/components/root/phi-config-provider.js +12 -1
- package/components/root/phi-root-background.d.ts +7 -0
- package/components/root/phi-root-background.js +20 -1
- package/components/root/phi-root-theme-resolver.js +3 -0
- package/components/root/phi-shell-chrome-overlay.d.ts +12 -1
- package/components/root/phi-shell-chrome-overlay.js +19 -3
- package/components/shell/phi-antd-icon.d.ts +1 -0
- package/components/shell/phi-antd-icon.js +3 -0
- package/components/shell/use-phi-shell-sticky-top.js +61 -16
- package/components/surface/phi-surface-tone.d.ts +13 -0
- package/components/surface/phi-surface-tone.js +21 -0
- package/components/widgets/client/bullet-list-entries-editor.js +7 -1
- package/components/widgets/client/html-editor.js +1 -1
- package/components/widgets/client/shared/use-page-reference-options.d.ts +17 -0
- package/components/widgets/client/shared/use-page-reference-options.js +57 -0
- package/components/widgets/config/background.d.ts +32 -1
- package/components/widgets/config/background.js +41 -2
- package/components/widgets/label-sets/background.js +2 -0
- package/components/widgets/label-types/background.d.ts +1 -0
- package/components/widgets/label-types/background.js +1 -0
- package/components/widgets/shared/bullet-list-vocabulary.d.ts +10 -0
- package/components/widgets/shared/bullet-list-vocabulary.js +5 -2
- package/components/widgets/shared/card-body-client.d.ts +5 -3
- package/components/widgets/shared/card-body-client.js +49 -6
- package/components/widgets/shared/card-vocabulary.d.ts +12 -2
- package/components/widgets/shared/card-vocabulary.js +7 -0
- package/components/widgets/shared/markdown-body-client.js +39 -10
- package/helpers/cms-container-chrome.js +6 -1
- package/helpers/locale.d.ts +8 -0
- package/helpers/locale.js +18 -0
- package/helpers/shell-region-style.js +2 -1
- package/helpers/surface-style.d.ts +12 -1
- package/helpers/surface-style.js +28 -2
- package/module-dashboard-cards.d.ts +1 -1
- package/module-dashboard-cards.js +1 -1
- package/package.json +1 -1
- package/plugins/runtime-modules/area-definitions.d.ts +0 -42
- package/plugins/runtime-modules/area-definitions.js +3 -8
- package/plugins/runtime-modules/builder/clients/inspector-config-field.js +20 -1
- package/plugins/runtime-modules/builder/clients/layout-scaffold-overlays.js +10 -5
- package/plugins/runtime-modules/builder/edit-scaffold-drawer.js +3 -2
- package/plugins/runtime-modules/builder/render-root-node-scaffold.js +25 -15
- package/plugins/runtime-modules/builder/trees/phi-default-builder-area-preset-tree.js +4 -0
- package/plugins/runtime-modules/builder/widgets/structure-region/built-in.js +15 -4
- package/plugins/runtime-modules/client-authoring-module.d.ts +8 -1
- package/plugins/runtime-modules/client-authoring-module.js +13 -3
- package/plugins/runtime-modules/core/widgets/brand/client.d.ts +12 -3
- package/plugins/runtime-modules/core/widgets/brand/client.js +3 -11
- package/plugins/runtime-modules/core/widgets/brand/home.d.ts +15 -0
- package/plugins/runtime-modules/core/widgets/brand/home.js +18 -0
- package/plugins/runtime-modules/core/widgets/brand/plugin.js +3 -2
- package/plugins/runtime-modules/core/widgets/brand/server.d.ts +2 -2
- package/plugins/runtime-modules/core/widgets/brand/server.js +2 -2
- package/plugins/runtime-modules/core/widgets/card/config.d.ts +6 -1
- package/plugins/runtime-modules/core/widgets/card/config.js +19 -3
- package/plugins/runtime-modules/core/widgets/card/editor.js +13 -3
- package/plugins/runtime-modules/core/widgets/card/server.js +1 -0
- package/plugins/runtime-modules/core/widgets/description/client.js +3 -3
- package/plugins/runtime-modules/core/widgets/description/config.d.ts +1 -0
- package/plugins/runtime-modules/core/widgets/description/editor.js +3 -2
- package/plugins/runtime-modules/core/widgets/description/server.js +3 -2
- package/plugins/runtime-modules/core/widgets/entry-list/config.d.ts +1 -0
- package/plugins/runtime-modules/core/widgets/gallery/config.d.ts +1 -0
- package/plugins/runtime-modules/core/widgets/header-navigation/client.js +2 -2
- package/plugins/runtime-modules/core/widgets/html/client.js +1 -1
- package/plugins/runtime-modules/core/widgets/markdown/config.d.ts +320 -2
- package/plugins/runtime-modules/core/widgets/markdown/config.js +18 -4
- package/plugins/runtime-modules/core/widgets/markdown/editor.js +9 -1
- package/plugins/runtime-modules/core/widgets/markdown-toc/client.js +6 -1
- package/plugins/runtime-modules/core/widgets/markdown-toc/config.d.ts +14 -0
- package/plugins/runtime-modules/core/widgets/markdown-toc/config.js +14 -2
- package/plugins/runtime-modules/core/widgets/quick-links/authoring.js +7 -2
- package/plugins/runtime-modules/core/widgets/quick-links/client.js +2 -2
- package/plugins/runtime-modules/core/widgets/quick-links/items.d.ts +0 -2
- package/plugins/runtime-modules/core/widgets/quick-links/items.js +0 -15
- package/plugins/runtime-modules/core/widgets/result/config.d.ts +1 -0
- package/plugins/runtime-modules/core/widgets/sidebar-navigation/authoring.js +1 -1
- package/plugins/runtime-modules/core/widgets/sidebar-navigation/client.js +1 -1
- package/plugins/runtime-modules/core/widgets/simple-text/client.js +8 -3
- package/plugins/runtime-modules/core/widgets/simple-text/config.d.ts +1 -0
- package/plugins/runtime-modules/core/widgets/simple-text/editor.js +2 -2
- package/plugins/runtime-modules/core/widgets/table/config.d.ts +3 -0
- package/plugins/runtime-modules/dashboard/card-rows.d.ts +3 -1
- package/plugins/runtime-modules/dashboard/card-rows.js +30 -2
- package/plugins/runtime-modules/dashboard/clients/dashboard-card-view.js +29 -7
- package/plugins/runtime-modules/dashboard/trees/phi-default-builder-dashboard-page-tree.js +5 -40
- package/plugins/runtime-modules/theme/widgets/brand-controls/client.js +19 -15
- package/plugins/runtime-modules/theme/widgets/brand-controls/config.js +5 -9
- package/server-helpers/cms-link-targets.js +6 -0
- package/styles/controls.css +64 -2
- package/styles/layout.css +45 -2
- package/styles/shell.css +73 -0
- package/theme/phi-antd-token-resolver.js +20 -1
- package/theme/phi-theme-tokens.d.ts +1 -1
- package/theme/phi-theme-tokens.js +1 -0
- package/theme/phi-theme.d.ts +5 -0
- package/theme/phi-theme.js +21 -0
- package/theme/phi-tokens.d.ts +16 -15
- package/theme/phi-tokens.js +16 -15
- package/types/cms-plugins.d.ts +2 -0
- package/types/dashboard-cards.d.ts +39 -4
- package/types/dashboard-cards.js +6 -0
- package/types/site-theme.d.ts +10 -2
|
@@ -38,6 +38,7 @@ const PHI_ANTD_ICON_LOADERS = {
|
|
|
38
38
|
CalendarOutlined: () => import("@ant-design/icons-svg/es/asn/CalendarOutlined"),
|
|
39
39
|
CheckOutlined: () => import("@ant-design/icons-svg/es/asn/CheckOutlined"),
|
|
40
40
|
CloseOutlined: () => import("@ant-design/icons-svg/es/asn/CloseOutlined"),
|
|
41
|
+
CloudDownloadOutlined: () => import("@ant-design/icons-svg/es/asn/CloudDownloadOutlined"),
|
|
41
42
|
CloudUploadOutlined: () => import("@ant-design/icons-svg/es/asn/CloudUploadOutlined"),
|
|
42
43
|
ClusterOutlined: () => import("@ant-design/icons-svg/es/asn/ClusterOutlined"),
|
|
43
44
|
CodeOutlined: () => import("@ant-design/icons-svg/es/asn/CodeOutlined"),
|
|
@@ -327,6 +328,8 @@ export const PHI_ANTD_ICON_NAMES = {
|
|
|
327
328
|
"video-camera-two-tone": "VideoCameraTwoTone",
|
|
328
329
|
warning: "WarningOutlined",
|
|
329
330
|
"warning-outlined": "WarningOutlined",
|
|
331
|
+
"cloud-download": "CloudDownloadOutlined",
|
|
332
|
+
"cloud-download-outlined": "CloudDownloadOutlined",
|
|
330
333
|
"cloud-upload": "CloudUploadOutlined",
|
|
331
334
|
"cloud-upload-outlined": "CloudUploadOutlined",
|
|
332
335
|
"eye-invisible": "EyeInvisibleOutlined",
|
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useEffect } from "react";
|
|
3
|
+
import { PHI_SHELL_STICKY_TOP_PROPERTY } from "../root/phi-shell-chrome-overlay";
|
|
3
4
|
/**
|
|
4
5
|
* How far down the viewport the Shell's Header holds right now, published on the Shell.
|
|
5
6
|
*
|
|
6
7
|
* Something that sticks to the top of the page -- a Table's header row -- has to stick under the Header
|
|
7
8
|
* rather than behind it, and where the Header ends is not a constant: each band sticks or scrolls by its
|
|
8
9
|
* own config, and a revealing `header_top` slides away while the visitor reads down and comes back when
|
|
9
|
-
* they scroll up. So the edge is measured, not configured: the lowest bottom of
|
|
10
|
-
* stuck
|
|
10
|
+
* they scroll up. So the edge is measured, not configured: the lowest visible bottom of the Header bands,
|
|
11
|
+
* stuck or in the flow, written as `--phi-shell-sticky-top` on the Shell element. Unset, a reader falls
|
|
11
12
|
* back to `0px`, which is right for a Shell without a sticky Header.
|
|
12
13
|
*
|
|
13
|
-
* Run by whatever sticks under it -- every sticky Table does
|
|
14
|
-
*
|
|
15
|
-
* Modal's or Drawer's body scrolls on
|
|
14
|
+
* Run by whatever sticks under it -- every sticky Table does, and the Region renderer for a stuck Sider
|
|
15
|
+
* (`phiCmsRegionSiderSticks`) -- so a page without one loads nothing for it. Each runner measures all the
|
|
16
|
+
* bands and they all write the same value. Not inside an Overlay: a Modal's or Drawer's body scrolls on
|
|
17
|
+
* its own, and its top edge is the line.
|
|
16
18
|
*/
|
|
17
|
-
const PHI_SHELL_STICKY_TOP_PROPERTY = "--phi-shell-sticky-top";
|
|
18
19
|
const HEADER_BANDS = '[data-phi-region-key^="header_"]:not([hidden])';
|
|
19
20
|
function measurePhiShellStickyTop(shell) {
|
|
21
|
+
/*
|
|
22
|
+
* The lowest visible bottom of the Header bands, stuck or not. Counting only the bands stuck at this
|
|
23
|
+
* moment made the edge jump: a band leaving its sticking line for its place in the flow -- the main
|
|
24
|
+
* band while the top band scrolls back in above it -- dropped out of the count at once, and whatever
|
|
25
|
+
* stuck under it leapt up by the band's height. Counted while it is in view, the edge moves with the
|
|
26
|
+
* scroll instead. A band in the flow stands above the content anyway, so nothing sticks too low for it.
|
|
27
|
+
*/
|
|
20
28
|
let bottom = 0;
|
|
21
29
|
for (const band of shell.querySelectorAll(HEADER_BANDS)) {
|
|
22
|
-
const style = window.getComputedStyle(band);
|
|
23
|
-
if (style.position !== "sticky" && style.position !== "fixed") {
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
30
|
const rect = band.getBoundingClientRect();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (rect.height === 0 || rect.top > stuckAt + 1 || rect.bottom <= 0) {
|
|
31
|
+
// Empty, or scrolled or slid out of the viewport.
|
|
32
|
+
if (rect.height === 0 || rect.bottom <= 0) {
|
|
30
33
|
continue;
|
|
31
34
|
}
|
|
32
35
|
bottom = Math.max(bottom, rect.bottom);
|
|
@@ -57,10 +60,46 @@ export function usePhiShellStickyTop({ active, elementRef, }) {
|
|
|
57
60
|
}
|
|
58
61
|
};
|
|
59
62
|
publish();
|
|
63
|
+
/*
|
|
64
|
+
* A revealing band moves by a transition. Measured only where it settled, whatever sticks under it
|
|
65
|
+
* would stand still while the band slides and then jump; so while a transition runs in the Shell the
|
|
66
|
+
* edge is measured every frame, and once more where it ends.
|
|
67
|
+
*/
|
|
68
|
+
let following = 0;
|
|
69
|
+
let followFrame = 0;
|
|
70
|
+
const follow = () => {
|
|
71
|
+
publish();
|
|
72
|
+
followFrame = following > 0 ? window.requestAnimationFrame(follow) : 0;
|
|
73
|
+
};
|
|
74
|
+
// Only a Header band's own transitions move the edge; a button's hover elsewhere in the Shell does not.
|
|
75
|
+
const isBandTransition = (event) => event.target instanceof Element && event.target.closest(HEADER_BANDS) !== null;
|
|
76
|
+
// A transition whose element leaves the page never ends; the follow stops on its own regardless.
|
|
77
|
+
let followTimeout = 0;
|
|
78
|
+
const stopFollowing = () => {
|
|
79
|
+
following = 0;
|
|
80
|
+
followTimeout = 0;
|
|
81
|
+
};
|
|
82
|
+
const transitionStarted = (event) => {
|
|
83
|
+
if (!isBandTransition(event))
|
|
84
|
+
return;
|
|
85
|
+
following += 1;
|
|
86
|
+
window.clearTimeout(followTimeout);
|
|
87
|
+
followTimeout = window.setTimeout(stopFollowing, 2000);
|
|
88
|
+
if (followFrame === 0) {
|
|
89
|
+
followFrame = window.requestAnimationFrame(follow);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const transitionStopped = (event) => {
|
|
93
|
+
if (!isBandTransition(event))
|
|
94
|
+
return;
|
|
95
|
+
following = Math.max(0, following - 1);
|
|
96
|
+
schedule();
|
|
97
|
+
};
|
|
60
98
|
window.addEventListener("scroll", schedule, { passive: true });
|
|
61
99
|
window.addEventListener("resize", schedule);
|
|
62
|
-
|
|
63
|
-
shell.addEventListener("transitionend",
|
|
100
|
+
shell.addEventListener("transitionrun", transitionStarted);
|
|
101
|
+
shell.addEventListener("transitionend", transitionStopped);
|
|
102
|
+
shell.addEventListener("transitioncancel", transitionStopped);
|
|
64
103
|
const observer = new ResizeObserver(schedule);
|
|
65
104
|
for (const band of shell.querySelectorAll(HEADER_BANDS)) {
|
|
66
105
|
observer.observe(band);
|
|
@@ -69,9 +108,15 @@ export function usePhiShellStickyTop({ active, elementRef, }) {
|
|
|
69
108
|
if (frame !== 0) {
|
|
70
109
|
window.cancelAnimationFrame(frame);
|
|
71
110
|
}
|
|
111
|
+
if (followFrame !== 0) {
|
|
112
|
+
window.cancelAnimationFrame(followFrame);
|
|
113
|
+
}
|
|
114
|
+
window.clearTimeout(followTimeout);
|
|
72
115
|
window.removeEventListener("scroll", schedule);
|
|
73
116
|
window.removeEventListener("resize", schedule);
|
|
74
|
-
shell.removeEventListener("
|
|
117
|
+
shell.removeEventListener("transitionrun", transitionStarted);
|
|
118
|
+
shell.removeEventListener("transitionend", transitionStopped);
|
|
119
|
+
shell.removeEventListener("transitioncancel", transitionStopped);
|
|
75
120
|
observer.disconnect();
|
|
76
121
|
};
|
|
77
122
|
}, [active, elementRef]);
|
|
@@ -16,3 +16,16 @@ export declare function PhiSurfaceTone({ tone, children, }: {
|
|
|
16
16
|
tone: PhiSurfaceToneValue | null | undefined;
|
|
17
17
|
children: ReactNode;
|
|
18
18
|
}): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* The page's own mode around something that belongs to the page's tools rather than to its content.
|
|
21
|
+
*
|
|
22
|
+
* The Builder draws its scaffolds -- tool bars, drag handles, the effects preview -- inside the Surfaces
|
|
23
|
+
* they edit, and a toned Surface would turn them with its content. This turns them back: inside a tone
|
|
24
|
+
* scope it mounts the page's mode again (its Ant Design components take it by themselves), outside one it
|
|
25
|
+
* renders the children as they are. `className` is the class an element of the caller's that reads the
|
|
26
|
+
* Theme's variables itself must carry, so they resolve in the page's mode there too; empty outside a tone.
|
|
27
|
+
*/
|
|
28
|
+
export declare function usePhiPageModeTone(): {
|
|
29
|
+
className: string | undefined;
|
|
30
|
+
wrap: (children: ReactNode) => ReactNode;
|
|
31
|
+
};
|
|
@@ -34,3 +34,24 @@ export function PhiSurfaceTone({ tone, children, }) {
|
|
|
34
34
|
}
|
|
35
35
|
return (_jsx(Suspense, { fallback: null, children: _jsx(PhiSurfaceToneScope, { mode: target, cssVarKey: cssVarKey, source: source, children: children }) }));
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* The page's own mode around something that belongs to the page's tools rather than to its content.
|
|
39
|
+
*
|
|
40
|
+
* The Builder draws its scaffolds -- tool bars, drag handles, the effects preview -- inside the Surfaces
|
|
41
|
+
* they edit, and a toned Surface would turn them with its content. This turns them back: inside a tone
|
|
42
|
+
* scope it mounts the page's mode again (its Ant Design components take it by themselves), outside one it
|
|
43
|
+
* renders the children as they are. `className` is the class an element of the caller's that reads the
|
|
44
|
+
* Theme's variables itself must carry, so they resolve in the page's mode there too; empty outside a tone.
|
|
45
|
+
*/
|
|
46
|
+
export function usePhiPageModeTone() {
|
|
47
|
+
const { mode } = usePhiConfig();
|
|
48
|
+
const source = usePhiThemeToneSource();
|
|
49
|
+
const pageMode = source?.pageMode ?? mode;
|
|
50
|
+
if (pageMode === mode) {
|
|
51
|
+
return { className: undefined, wrap: (children) => children };
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
className: resolvePhiSurfaceToneClassName(pageMode),
|
|
55
|
+
wrap: (children) => _jsx(PhiSurfaceTone, { tone: pageMode, children: children }),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -99,5 +99,11 @@ export function PhiBulletListEntriesEditor({ entries, listStyle, onCommit, state
|
|
|
99
99
|
resize: "none",
|
|
100
100
|
color: "inherit",
|
|
101
101
|
...fieldStyle,
|
|
102
|
-
}
|
|
102
|
+
},
|
|
103
|
+
/*
|
|
104
|
+
* The text's own padding, stated: half of what the line leaves of the field's height, so the
|
|
105
|
+
* first line's middle is the marker's (`markerLineHeight` above). That is the padding the
|
|
106
|
+
* field had from Ant Design already; stated, the clear button can find the line too.
|
|
107
|
+
*/
|
|
108
|
+
inputStyle: { paddingBlock: "calc((var(--ant-control-height) - 1lh) / 2)" } })), states && onCommit ? (_entry, index) => stepState(index) : undefined) }) }));
|
|
103
109
|
}
|
|
@@ -388,7 +388,7 @@ export function PhiHtmlWidgetEditor({ blockId, html, fontFamily, fontSize, onCha
|
|
|
388
388
|
const initialHtmlRef = useRef(normalizedHtml);
|
|
389
389
|
const latestHtmlRef = useRef(normalizedHtml);
|
|
390
390
|
const resolvedFontFamily = resolvePhiWidgetFontFamily(fontFamily, fonts, token);
|
|
391
|
-
const resolvedFontSize = resolvePhiWidgetFontSize(fontSize, token, "
|
|
391
|
+
const resolvedFontSize = resolvePhiWidgetFontSize(fontSize, token, "base");
|
|
392
392
|
const editable = onChange != null;
|
|
393
393
|
useEffect(() => {
|
|
394
394
|
latestHtmlRef.current = normalizedHtml;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PhiCmsAreaKey } from "../../../../constants/cms-areas";
|
|
2
|
+
import type { PhiTreeOption } from "../../../../types/tree";
|
|
3
|
+
import type { PhiPageReferenceSelection } from "./phi-page-reference-picker";
|
|
4
|
+
type PageReferenceOptions = PhiTreeOption<PhiPageReferenceSelection>[];
|
|
5
|
+
/**
|
|
6
|
+
* The Pages of one Area a link may point at, as the tree `PhiLinkTargetControl` offers, for a surface
|
|
7
|
+
* that stands outside the Builder's own catalog: a Form's `link-target` field, the Theme's Brand.
|
|
8
|
+
*
|
|
9
|
+
* Answered by `/api/site/page-references` (`gateway/page-references-route.ts`), which merges the Area's
|
|
10
|
+
* Module Pages and Core's the way the Builder's picker does. `failed` is set when the list could not be
|
|
11
|
+
* read, so the surface can say so instead of showing an empty list that looks like a Site without Pages.
|
|
12
|
+
*/
|
|
13
|
+
export declare function usePhiPageReferenceOptions(area: PhiCmsAreaKey, logScope: string): {
|
|
14
|
+
options: PageReferenceOptions;
|
|
15
|
+
failed: boolean;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
/*
|
|
4
|
+
* One request per Area for as long as the page lives: the create and the edit dialog offer the same
|
|
5
|
+
* Pages, and a list that changed meanwhile is a Page somebody published while this one was being written.
|
|
6
|
+
* A failed request is forgotten, so the next dialog asks again.
|
|
7
|
+
*/
|
|
8
|
+
const pageOptionsByArea = new Map();
|
|
9
|
+
function loadPageOptions(area) {
|
|
10
|
+
const cached = pageOptionsByArea.get(area);
|
|
11
|
+
if (cached)
|
|
12
|
+
return cached;
|
|
13
|
+
const request = (async () => {
|
|
14
|
+
const response = await fetch(`/api/site/page-references?area=${encodeURIComponent(area)}`, {
|
|
15
|
+
cache: "no-store",
|
|
16
|
+
credentials: "same-origin",
|
|
17
|
+
headers: { accept: "application/json" },
|
|
18
|
+
});
|
|
19
|
+
const payload = (await response.json().catch(() => null));
|
|
20
|
+
if (!response.ok || !Array.isArray(payload?.options)) {
|
|
21
|
+
throw new Error(`Page references for "${area}" answered ${response.status}.`);
|
|
22
|
+
}
|
|
23
|
+
return payload.options;
|
|
24
|
+
})();
|
|
25
|
+
pageOptionsByArea.set(area, request);
|
|
26
|
+
request.catch(() => pageOptionsByArea.delete(area));
|
|
27
|
+
return request;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The Pages of one Area a link may point at, as the tree `PhiLinkTargetControl` offers, for a surface
|
|
31
|
+
* that stands outside the Builder's own catalog: a Form's `link-target` field, the Theme's Brand.
|
|
32
|
+
*
|
|
33
|
+
* Answered by `/api/site/page-references` (`gateway/page-references-route.ts`), which merges the Area's
|
|
34
|
+
* Module Pages and Core's the way the Builder's picker does. `failed` is set when the list could not be
|
|
35
|
+
* read, so the surface can say so instead of showing an empty list that looks like a Site without Pages.
|
|
36
|
+
*/
|
|
37
|
+
export function usePhiPageReferenceOptions(area, logScope) {
|
|
38
|
+
const [options, setOptions] = useState([]);
|
|
39
|
+
const [failed, setFailed] = useState(false);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
let current = true;
|
|
42
|
+
loadPageOptions(area).then((next) => {
|
|
43
|
+
if (current) {
|
|
44
|
+
setOptions(next);
|
|
45
|
+
setFailed(false);
|
|
46
|
+
}
|
|
47
|
+
}, (error) => {
|
|
48
|
+
console.warn(`[${logScope}] Page references could not be loaded.`, { area, error });
|
|
49
|
+
if (current)
|
|
50
|
+
setFailed(true);
|
|
51
|
+
});
|
|
52
|
+
return () => {
|
|
53
|
+
current = false;
|
|
54
|
+
};
|
|
55
|
+
}, [area, logScope]);
|
|
56
|
+
return { options, failed };
|
|
57
|
+
}
|
|
@@ -18,6 +18,19 @@ export type PhiBackgroundBaseColor = {
|
|
|
18
18
|
export type PhiBackgroundBaseNone = {
|
|
19
19
|
kind: "none";
|
|
20
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* The Theme's Chrome Overlay as the ground (LAYOUTING.md "Surface tone", SHELL.md "Root Background and
|
|
23
|
+
* Shell Backdrop Layers").
|
|
24
|
+
*
|
|
25
|
+
* The box paints what the Theme workspace calls the Chrome Overlay -- its colour or picture and its glass
|
|
26
|
+
* pane -- instead of a ground of its own. It carries nothing else: the Overlay is the Theme's, per mode
|
|
27
|
+
* (`root.chrome[mode]`), and the box reads the mode it stands in. A Surface `tone` is that mode, so an
|
|
28
|
+
* `inverse` Footer is the dark Overlay on a light page and the light one on a dark page. It is a stated
|
|
29
|
+
* ground: a tone does not paint its own container ground beneath it.
|
|
30
|
+
*/
|
|
31
|
+
export type PhiBackgroundBaseTheme = {
|
|
32
|
+
kind: "theme";
|
|
33
|
+
};
|
|
21
34
|
export type PhiBackgroundBaseGradient = {
|
|
22
35
|
kind: "gradient";
|
|
23
36
|
direction: PhiBackgroundDirection;
|
|
@@ -50,6 +63,24 @@ export type PhiBackgroundImageSourceKind = "asset" | "url";
|
|
|
50
63
|
*/
|
|
51
64
|
export declare const PHI_BACKGROUND_IMAGE_SOURCE_KINDS: readonly PhiBackgroundImageSourceKind[];
|
|
52
65
|
export declare const PHI_BACKGROUND_BASE_KINDS: readonly PhiCmsBackgroundWidgetConfig["base"]["kind"][];
|
|
66
|
+
/**
|
|
67
|
+
* The container ground of the mode a box stands in. What a toned Surface paints where it states no
|
|
68
|
+
* ground (`PHI_SURFACE_TONE_GROUND`), and what a `theme` ground falls back to where the Theme's Overlay
|
|
69
|
+
* is not published.
|
|
70
|
+
*/
|
|
71
|
+
export declare const PHI_BACKGROUND_CONTAINER_GROUND = "var(--ant-color-bg-container)";
|
|
72
|
+
/**
|
|
73
|
+
* What a `theme` Base paints: the Shell Chrome Overlay's custom properties, the same ones the Shell's
|
|
74
|
+
* chrome panes paint from (components/shell/phi-cms-shell.tsx).
|
|
75
|
+
*
|
|
76
|
+
* The Root Layout publishes them for the page's mode, and `styles/shell.css` publishes them again under a
|
|
77
|
+
* tone's class for the tone's mode, so the box needs no colour of its own and turns with the mode switch.
|
|
78
|
+
* Outside the Root Layout -- a Modal or Drawer portalled to `<body>` -- nothing publishes them, and the box
|
|
79
|
+
* takes the container ground of its mode instead: the tone's ground, or the Overlay's own where it has no
|
|
80
|
+
* tone. Fixed attachment for the reason the Chrome Regions have it: every box is a window onto one
|
|
81
|
+
* viewport-sized painting.
|
|
82
|
+
*/
|
|
83
|
+
export declare const PHI_BACKGROUND_THEME_STYLE: Readonly<CSSProperties>;
|
|
53
84
|
export declare const PHI_BACKGROUND_MOTION_MODES: readonly PhiBackgroundMotionMode[];
|
|
54
85
|
export type PhiBackgroundMotionDirection = "natural" | "reverse";
|
|
55
86
|
/**
|
|
@@ -103,7 +134,7 @@ export type PhiBackgroundOverlay = PhiBackgroundColorOverlay | PhiBackgroundPatt
|
|
|
103
134
|
/** What an Overlay that states no opacity is painted at, shared by every kind. */
|
|
104
135
|
export declare const PHI_BACKGROUND_OVERLAY_DEFAULT_OPACITY = 0.14;
|
|
105
136
|
export type PhiCmsBackgroundWidgetConfig = {
|
|
106
|
-
base: PhiBackgroundBaseColor | PhiBackgroundBaseGradient | PhiBackgroundBaseImage | PhiBackgroundBaseNone;
|
|
137
|
+
base: PhiBackgroundBaseColor | PhiBackgroundBaseGradient | PhiBackgroundBaseImage | PhiBackgroundBaseNone | PhiBackgroundBaseTheme;
|
|
107
138
|
overlay?: PhiBackgroundOverlay | null;
|
|
108
139
|
filter?: PhiBackgroundFilter | null;
|
|
109
140
|
motion?: PhiBackgroundMotion | null;
|
|
@@ -30,7 +30,35 @@ export const PHI_BACKGROUND_BASE_KINDS = [
|
|
|
30
30
|
"color",
|
|
31
31
|
"gradient",
|
|
32
32
|
"image",
|
|
33
|
+
"theme",
|
|
33
34
|
];
|
|
35
|
+
/**
|
|
36
|
+
* The container ground of the mode a box stands in. What a toned Surface paints where it states no
|
|
37
|
+
* ground (`PHI_SURFACE_TONE_GROUND`), and what a `theme` ground falls back to where the Theme's Overlay
|
|
38
|
+
* is not published.
|
|
39
|
+
*/
|
|
40
|
+
export const PHI_BACKGROUND_CONTAINER_GROUND = "var(--ant-color-bg-container)";
|
|
41
|
+
/**
|
|
42
|
+
* What a `theme` Base paints: the Shell Chrome Overlay's custom properties, the same ones the Shell's
|
|
43
|
+
* chrome panes paint from (components/shell/phi-cms-shell.tsx).
|
|
44
|
+
*
|
|
45
|
+
* The Root Layout publishes them for the page's mode, and `styles/shell.css` publishes them again under a
|
|
46
|
+
* tone's class for the tone's mode, so the box needs no colour of its own and turns with the mode switch.
|
|
47
|
+
* Outside the Root Layout -- a Modal or Drawer portalled to `<body>` -- nothing publishes them, and the box
|
|
48
|
+
* takes the container ground of its mode instead: the tone's ground, or the Overlay's own where it has no
|
|
49
|
+
* tone. Fixed attachment for the reason the Chrome Regions have it: every box is a window onto one
|
|
50
|
+
* viewport-sized painting.
|
|
51
|
+
*/
|
|
52
|
+
export const PHI_BACKGROUND_THEME_STYLE = {
|
|
53
|
+
backgroundColor: `var(--phi-shell-chrome-color, ${PHI_BACKGROUND_CONTAINER_GROUND})`,
|
|
54
|
+
backgroundImage: "var(--phi-shell-chrome-image, none)",
|
|
55
|
+
backgroundSize: "var(--phi-shell-chrome-size, auto)",
|
|
56
|
+
backgroundPosition: "var(--phi-shell-chrome-position, 0 0)",
|
|
57
|
+
backgroundRepeat: "var(--phi-shell-chrome-repeat, repeat)",
|
|
58
|
+
backgroundAttachment: "fixed",
|
|
59
|
+
backdropFilter: "var(--phi-shell-chrome-filter, none)",
|
|
60
|
+
WebkitBackdropFilter: "var(--phi-shell-chrome-filter, none)",
|
|
61
|
+
};
|
|
34
62
|
export const PHI_BACKGROUND_MOTION_MODES = [
|
|
35
63
|
"static",
|
|
36
64
|
"fixed",
|
|
@@ -145,7 +173,7 @@ function normalizePhiBackgroundBase(value) {
|
|
|
145
173
|
? { ...normalized, ...source }
|
|
146
174
|
: { ...normalized, ...source };
|
|
147
175
|
}
|
|
148
|
-
if (kind === "none")
|
|
176
|
+
if (kind === "none" || kind === "theme")
|
|
149
177
|
return { kind };
|
|
150
178
|
if (kind === "color") {
|
|
151
179
|
const color = readString(raw.color);
|
|
@@ -253,6 +281,10 @@ export function normalizePhiBackgroundWidgetConfig(config) {
|
|
|
253
281
|
}
|
|
254
282
|
const raw = config;
|
|
255
283
|
const base = normalizePhiBackgroundBase(raw.base) ?? normalizePhiBackgroundBase(raw) ?? { kind: "none" };
|
|
284
|
+
// The Theme's Overlay is the whole ground: it carries its own Overlay and pane, and takes none of the box's.
|
|
285
|
+
if (base.kind === "theme") {
|
|
286
|
+
return { base, overlay: null, filter: null, motion: null };
|
|
287
|
+
}
|
|
256
288
|
return {
|
|
257
289
|
base,
|
|
258
290
|
overlay: readBackgroundOverlay(raw.overlay),
|
|
@@ -278,6 +310,9 @@ export function readPhiBackgroundPatternInkFromCss(css) {
|
|
|
278
310
|
export function serializePhiBackgroundBaseCss(base) {
|
|
279
311
|
if (base.kind === "none")
|
|
280
312
|
return "none";
|
|
313
|
+
// Not a value a Base can be read back from: the Overlay's colour, as far as one CSS value can say it.
|
|
314
|
+
if (base.kind === "theme")
|
|
315
|
+
return String(PHI_BACKGROUND_THEME_STYLE.backgroundColor);
|
|
281
316
|
if (base.kind === "color")
|
|
282
317
|
return base.color;
|
|
283
318
|
if (base.kind === "gradient") {
|
|
@@ -320,6 +355,9 @@ function resolvePhiBackgroundOverlayLayer(overlay) {
|
|
|
320
355
|
export function resolvePhiBackgroundWidgetStyle(config) {
|
|
321
356
|
const normalized = normalizePhiBackgroundWidgetConfig(config);
|
|
322
357
|
const base = normalized.base;
|
|
358
|
+
if (base.kind === "theme") {
|
|
359
|
+
return { ...PHI_BACKGROUND_THEME_STYLE };
|
|
360
|
+
}
|
|
323
361
|
const style = {};
|
|
324
362
|
if (base.kind === "color")
|
|
325
363
|
style.backgroundColor = base.color;
|
|
@@ -397,7 +435,8 @@ export function phiBackgroundWidgetConfigPaintsGround(config) {
|
|
|
397
435
|
* nor honoured there.
|
|
398
436
|
*/
|
|
399
437
|
export function phiBackgroundBaseSupportsGlassEffect(base) {
|
|
400
|
-
|
|
438
|
+
// The Theme's Overlay brings its own pane, and a second one over it is nothing anybody asked for.
|
|
439
|
+
return base.kind !== "image" && base.kind !== "gradient" && base.kind !== "theme";
|
|
401
440
|
}
|
|
402
441
|
/**
|
|
403
442
|
* Whether `blur` has anything to soften: a picture, a gradient, or a patterned Overlay.
|
|
@@ -18,6 +18,7 @@ const PHI_BACKGROUND_WIDGET_LABEL_SET = definePhiLabelSet({
|
|
|
18
18
|
base_color: PHI_BACKGROUND_WIDGET_DEFAULT_LABELS.base.color,
|
|
19
19
|
base_gradient: PHI_BACKGROUND_WIDGET_DEFAULT_LABELS.base.gradient,
|
|
20
20
|
base_image: PHI_BACKGROUND_WIDGET_DEFAULT_LABELS.base.image,
|
|
21
|
+
base_theme: PHI_BACKGROUND_WIDGET_DEFAULT_LABELS.base.theme,
|
|
21
22
|
image_url: PHI_BACKGROUND_WIDGET_DEFAULT_LABELS.image.url,
|
|
22
23
|
image_alt: PHI_BACKGROUND_WIDGET_DEFAULT_LABELS.image.alt,
|
|
23
24
|
image_position: PHI_BACKGROUND_WIDGET_DEFAULT_LABELS.image.position,
|
|
@@ -104,6 +105,7 @@ export async function getPhiBackgroundWidgetLabels(options) {
|
|
|
104
105
|
color: labels.base_color,
|
|
105
106
|
gradient: labels.base_gradient,
|
|
106
107
|
image: labels.base_image,
|
|
108
|
+
theme: labels.base_theme,
|
|
107
109
|
},
|
|
108
110
|
image: {
|
|
109
111
|
url: labels.image_url,
|
|
@@ -41,6 +41,16 @@ export type PhiResolvedBulletListStyle = {
|
|
|
41
41
|
gap: PhiSpacingScaleKey;
|
|
42
42
|
flags: number;
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* The markers a browser cycles through, and a tight gap. Exported so a Widget can state them as its
|
|
46
|
+
* defaults, which is what an Inspector shows for a list nobody has set yet.
|
|
47
|
+
*/
|
|
48
|
+
export declare const PHI_BULLET_LIST_DEFAULT_STYLE: {
|
|
49
|
+
readonly level1: "dot";
|
|
50
|
+
readonly level2: "circle";
|
|
51
|
+
readonly level3: "dash";
|
|
52
|
+
readonly gap: "xs";
|
|
53
|
+
};
|
|
44
54
|
/** A stored list style, part by part; whatever is not one of its values is left out. */
|
|
45
55
|
export declare function readPhiBulletListStyle(value: unknown): PhiBulletListStyle | undefined;
|
|
46
56
|
export declare function resolvePhiBulletListStyle(style: PhiBulletListStyle | null | undefined): PhiResolvedBulletListStyle;
|
|
@@ -20,8 +20,11 @@ export const PhiBulletListFlag = {
|
|
|
20
20
|
/** A line between entries, for entries without a ground of their own. */
|
|
21
21
|
Dividers: 1 << 0,
|
|
22
22
|
};
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The markers a browser cycles through, and a tight gap. Exported so a Widget can state them as its
|
|
25
|
+
* defaults, which is what an Inspector shows for a list nobody has set yet.
|
|
26
|
+
*/
|
|
27
|
+
export const PHI_BULLET_LIST_DEFAULT_STYLE = {
|
|
25
28
|
level1: "dot",
|
|
26
29
|
level2: "circle",
|
|
27
30
|
level3: "dash",
|
|
@@ -4,8 +4,8 @@ import type { PhiImagePresentation } from "../../media/image-presentation";
|
|
|
4
4
|
import type { PhiClientBlockBaseProps } from "../../../types";
|
|
5
5
|
import type { PhiSurface } from "../../../types/surface";
|
|
6
6
|
import type { PhiSignalRouteSet } from "../../../types/signals";
|
|
7
|
-
import type { PhiCardBillingPeriod, PhiCardHeadingLevel, PhiCardHoverEffect, PhiCardIconPlacement, PhiCardTextAlign, PhiCardTextSlot, PhiCardVariant, PhiCardWidgetBody } from "./card-vocabulary";
|
|
8
|
-
export type { PhiCardBillingPeriod, PhiCardHeadingLevel, PhiCardHoverEffect, PhiCardIconPlacement, PhiCardTextAlign, PhiCardTextSlot, PhiCardVariant, PhiCardWidgetBody, } from "./card-vocabulary";
|
|
7
|
+
import type { PhiCardBadgeStyle, PhiCardBillingPeriod, PhiCardHeadingLevel, PhiCardHoverEffect, PhiCardIconPlacement, PhiCardTextAlign, PhiCardTextSlot, PhiCardVariant, PhiCardWidgetBody } from "./card-vocabulary";
|
|
8
|
+
export type { PhiCardBadgeStyle, PhiCardBillingPeriod, PhiCardHeadingLevel, PhiCardHoverEffect, PhiCardIconPlacement, PhiCardTextAlign, PhiCardTextSlot, PhiCardVariant, PhiCardWidgetBody, } from "./card-vocabulary";
|
|
9
9
|
export type PhiCardWidgetClientLabels = {
|
|
10
10
|
eyebrow?: string;
|
|
11
11
|
/**
|
|
@@ -72,6 +72,8 @@ export type PhiCardWidgetClientConfig = {
|
|
|
72
72
|
highlight?: boolean;
|
|
73
73
|
/** The badge's ground; absent, the Theme's primary. */
|
|
74
74
|
badgeColor?: string;
|
|
75
|
+
/** How the badge is drawn; absent, `none`. */
|
|
76
|
+
badgeStyle?: PhiCardBadgeStyle;
|
|
75
77
|
/** Which price a `plan` shows until a `billingPeriod` signal says otherwise. Absent is `monthly`. */
|
|
76
78
|
billingPeriod?: PhiCardBillingPeriod;
|
|
77
79
|
hoverEffect?: PhiCardHoverEffect;
|
|
@@ -89,7 +91,7 @@ export type PhiCardWidgetAction = {
|
|
|
89
91
|
export type PhiCardWidgetClientProps = PhiClientBlockBaseProps<PhiCardWidgetClientLabels, PhiCardWidgetClientConfig> & {
|
|
90
92
|
binding?: PhiCardWidgetClientBinding;
|
|
91
93
|
/**
|
|
92
|
-
* What a `list` body draws, and a `plan` body's features, already drawn.
|
|
94
|
+
* What a `list` body draws, a `series` body's chart, and a `plan` body's features, already drawn.
|
|
93
95
|
*
|
|
94
96
|
* Handed in rather than drawn here: the Card is on Public pages, and a list Control imported by it
|
|
95
97
|
* would load on every page with a Card. A Dashboard draws its entries and passes them; a plan card's
|
|
@@ -5,6 +5,7 @@ import { PhiButtonControl } from "../../controls/phi-button-control";
|
|
|
5
5
|
import { PhiFlexControl } from "../../controls/phi-flex-control";
|
|
6
6
|
import { PhiImageControl } from "../../controls/phi-image-control";
|
|
7
7
|
import { PhiRibbonControl } from "../../controls/phi-ribbon-control";
|
|
8
|
+
import { PhiRibbonWrapControl } from "../../controls/phi-ribbon-wrap-control";
|
|
8
9
|
import { PhiSkeletonControl } from "../../controls/phi-skeleton-control";
|
|
9
10
|
import { PhiStatisticControl } from "../../controls/phi-statistic-control";
|
|
10
11
|
import { PhiTypographyControl } from "../../controls/phi-typography-control";
|
|
@@ -16,6 +17,7 @@ import { PhiSurfaceTone } from "../../surface/phi-surface-tone";
|
|
|
16
17
|
import { resolvePhiSurfaceStyle } from "../../../helpers/surface-style";
|
|
17
18
|
import { usePhiControlSignalController } from "../client/shared/phi-control-signals";
|
|
18
19
|
import { PHI_COLOR, PHI_SPACE } from "../../../theme/antd-css-var-contract";
|
|
20
|
+
import { PHI_THEME_HEADING_FONT_VARIABLE } from "../../../theme/phi-theme-typography";
|
|
19
21
|
import { PHI_CARD_TEXT_SLOTS, readPhiCardBillingPeriod } from "./card-vocabulary";
|
|
20
22
|
const PHI_CARD_SIGNAL_TEXT_SLOTS = new Set(PHI_CARD_TEXT_SLOTS);
|
|
21
23
|
/*
|
|
@@ -192,6 +194,8 @@ export function PhiCardWidgetClient({ labels: placedLabels, config, binding, lis
|
|
|
192
194
|
* Segmented's items follow their track. The Theme's plain corner did not: a pill Site drew square marks.
|
|
193
195
|
*/
|
|
194
196
|
const iconCorner = (side) => `calc(var(--phi-control-radius-lg, ${token.borderRadiusLG}px) * ${side / token.controlHeightLG})`;
|
|
197
|
+
/* The framed box an icon before the title stands in. */
|
|
198
|
+
const inlineIconFrame = iconSize + 16;
|
|
195
199
|
const iconMark = (size, framed) => iconName ? (_jsx("span", { "aria-hidden": "true", className: "phi-card__icon", style: {
|
|
196
200
|
display: "inline-flex",
|
|
197
201
|
flex: "none",
|
|
@@ -207,10 +211,34 @@ export function PhiCardWidgetClient({ labels: placedLabels, config, binding, lis
|
|
|
207
211
|
* unreadable. A card whose figure is the only thing it has makes the figure the link.
|
|
208
212
|
*/
|
|
209
213
|
const valueText = renderText ? renderText("value", labels.value) : null;
|
|
210
|
-
|
|
214
|
+
/*
|
|
215
|
+
* Beside an icon before the title, the figure starts where the title's words do, under them rather
|
|
216
|
+
* than under the icon: the number belongs to the heading, the icon only marks it. On a line set to its
|
|
217
|
+
* start only -- centred or at the end, the figure already stands with the words.
|
|
218
|
+
*/
|
|
219
|
+
const figureIndent = iconPlacement === "inline" && iconName && (textAlign === "start" || textAlign === "justify")
|
|
220
|
+
? { paddingInlineStart: inlineIconFrame + gap }
|
|
221
|
+
: {};
|
|
222
|
+
/*
|
|
223
|
+
* The figure is set like the title it belongs to: in the font the Theme gives headings, and with digits
|
|
224
|
+
* of one width, so a figure that changes does not shift as it counts. No fallback in the `var()`, as for
|
|
225
|
+
* the headings themselves (`styles/layout.css`): a Theme that keeps headings on the body font sets no
|
|
226
|
+
* variable, and the figure inherits the body font.
|
|
227
|
+
*/
|
|
228
|
+
const figureType = {
|
|
229
|
+
fontFamily: `var(${PHI_THEME_HEADING_FONT_VARIABLE})`,
|
|
230
|
+
fontVariantNumeric: "tabular-nums",
|
|
231
|
+
};
|
|
232
|
+
const figure = valueText != null ? (_jsx("div", { style: {
|
|
233
|
+
...figureType,
|
|
234
|
+
fontSize: token.fontSizeHeading3,
|
|
235
|
+
color: highlight ? PHI_COLOR.primary : PHI_COLOR.textHeading,
|
|
236
|
+
...slotWidth,
|
|
237
|
+
...figureIndent,
|
|
238
|
+
}, children: valueText })) : (_jsx(PhiStatisticControl, { value: binding?.error ?? labels.value ?? "", loading: binding?.loading === true || overrides.loading === true, style: figureIndent, styles: {
|
|
211
239
|
content: binding?.error
|
|
212
240
|
? { color: PHI_COLOR.error, fontSize: token.fontSize }
|
|
213
|
-
: { color: highlight ? PHI_COLOR.primary : PHI_COLOR.textHeading },
|
|
241
|
+
: { ...figureType, color: highlight ? PHI_COLOR.primary : PHI_COLOR.textHeading },
|
|
214
242
|
} }));
|
|
215
243
|
/* A failure stands where the lines would, as it does for a figure; a list still coming is three lines. */
|
|
216
244
|
const list = binding?.error ? (_jsx(PhiTypographyControl, { style: { color: PHI_COLOR.error, ...slotWidth }, children: binding.error })) : binding?.loading === true || overrides.loading === true ? (_jsx(PhiSkeletonControl, { lines: 3 })) : listContent ?? null;
|
|
@@ -230,7 +258,7 @@ export function PhiCardWidgetClient({ labels: placedLabels, config, binding, lis
|
|
|
230
258
|
color: highlight ? PHI_COLOR.primary : PHI_COLOR.textHeading,
|
|
231
259
|
...slotWidth,
|
|
232
260
|
}, children: priceText })) : null, periodText != null ? (_jsx("span", { style: { color: PHI_COLOR.textSecondary, ...slotWidth }, children: periodText })) : null] })) : null, list != null ? _jsx("div", { style: { justifySelf: "stretch", minWidth: 0, textAlign: "start" }, children: list }) : null] })) : null;
|
|
233
|
-
const headingRow = iconPlacement === "inline" && iconName ? (_jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: justify, gap, ...slotWidth }, children: [iconMark(iconSize,
|
|
261
|
+
const headingRow = iconPlacement === "inline" && iconName ? (_jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: justify, gap, ...slotWidth }, children: [iconMark(iconSize, inlineIconFrame), heading] })) : heading;
|
|
234
262
|
const media = image ? (_jsxs("div", { className: "phi-card__media", style: {
|
|
235
263
|
position: "relative",
|
|
236
264
|
aspectRatio: metrics.mediaAspectRatio ?? resolveMediaAspectRatio(image.presentation),
|
|
@@ -294,19 +322,34 @@ export function PhiCardWidgetClient({ labels: placedLabels, config, binding, lis
|
|
|
294
322
|
* The band in the card's top end corner, floating (`PhiRibbonControl`): the words below it start where
|
|
295
323
|
* they start in a card without one, so plans side by side keep their headings on one line.
|
|
296
324
|
*/
|
|
297
|
-
const
|
|
298
|
-
const
|
|
325
|
+
const badgeStyle = config?.badgeStyle ?? "none";
|
|
326
|
+
const ribbon = badge != null && (badgeStyle === "corner" || badgeStyle === "tag") ? (_jsx(PhiRibbonControl, { color: config?.badgeColor, placement: badgeStyle === "tag" ? "edge" : "corner", ...(!renderText && labels.badge
|
|
327
|
+
? {
|
|
328
|
+
fullText: labels.badge,
|
|
329
|
+
...(href
|
|
330
|
+
? {
|
|
331
|
+
onClick: (event) => event.currentTarget.closest(".phi-card")?.querySelector(".phi-card__link")?.click(),
|
|
332
|
+
}
|
|
333
|
+
: {}),
|
|
334
|
+
}
|
|
335
|
+
: {}), ...(renderText ? { style: { pointerEvents: "auto", minWidth: "6em" } } : {}), children: badge })) : null;
|
|
336
|
+
const words = (_jsxs("div", { style: { display: "grid", gap, padding: footAction ? 0 : inset, justifyItems }, children: [iconAtTop && iconName && !image ? (_jsx("div", { style: { justifySelf: markSide === "center" ? "center" : markSide === "end" ? "end" : "start" }, children: iconMark(topIconSize, topIconSize + 24) })) : null, eyebrow != null && !eyebrowOnMedia && !labels.eyebrowEnd ? (_jsx(PhiTypographyControl, { type: "secondary", style: { ...eyebrowType, color: highlight ? PHI_COLOR.primary : PHI_COLOR.textTertiary, ...slotWidth }, children: eyebrow })) : null, labels.eyebrowEnd && !eyebrowOnMedia ? (_jsxs(PhiFlexControl, { justify: "space-between", align: "baseline", wrap: true, gap: PHI_SPACE.xs, style: { justifySelf: "stretch", minWidth: 0 }, children: [_jsx(PhiTypographyControl, { type: "secondary", style: { ...eyebrowType, color: highlight ? PHI_COLOR.primary : PHI_COLOR.textTertiary }, children: eyebrow ?? "" }), _jsx(PhiTypographyControl, { type: "secondary", style: { ...eyebrowType, color: PHI_COLOR.textTertiary }, children: labels.eyebrowEnd })] })) : null, headingRow, body === "stat" ? figure : null, body === "series" ? (_jsxs(_Fragment, { children: [labels.value || binding?.error || binding?.loading === true || overrides.loading === true ? figure : null, list != null && !binding?.error && binding?.loading !== true && overrides.loading !== true ? (_jsx("div", { style: { justifySelf: "stretch", minWidth: 0 }, children: list })) : null] })) : null, body === "list" && list != null ? (_jsx("div", { style: { justifySelf: "stretch", minWidth: 0 }, children: list })) : null, description != null ? (_jsx(PhiTypographyControl, { presentation: "paragraph", style: {
|
|
299
337
|
marginBottom: 0,
|
|
300
338
|
color: PHI_COLOR.textSecondary,
|
|
301
339
|
fontSize: descriptionSize,
|
|
302
340
|
...slotWidth,
|
|
303
341
|
}, children: description })) : null, plan, meta != null ? (_jsx(PhiTypographyControl, { type: "secondary", style: { fontSize: token.fontSizeSM, color: PHI_COLOR.textTertiary, ...slotWidth }, children: meta })) : null, footAction ? null : actionButton] }));
|
|
304
342
|
const content = (_jsxs(_Fragment, { children: [media, footAction ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap, padding: inset, flex: "1 1 auto", minWidth: 0 }, children: [words, actionButton] })) : words, cardLink] }));
|
|
305
|
-
|
|
343
|
+
const card = (_jsxs("article", { className: [
|
|
306
344
|
"phi-card",
|
|
307
345
|
href ? "phi-card--link" : null,
|
|
308
346
|
highlight ? "phi-card--highlight" : null,
|
|
309
347
|
hoverEffect !== "none" ? `phi-card--hover-${hoverEffect}` : null,
|
|
310
348
|
surface.className,
|
|
311
349
|
].filter(Boolean).join(" "), "data-phi-card-variant": variant, style: boxStyle, children: [_jsx(PhiSurfaceGroundLayer, { ground: surface.ground, className: "phi-card__ground" }), ribbon, surface.className ? _jsx(PhiSurfaceTone, { tone: config?.surface?.tone, children: content }) : content] }));
|
|
350
|
+
/*
|
|
351
|
+
* A ribbon hangs over the card's edge, which the card -- clipping for its picture and its zoom -- cannot
|
|
352
|
+
* give a child of its own; it wraps the card instead (`PhiRibbonWrapControl`). An editor writes in it.
|
|
353
|
+
*/
|
|
354
|
+
return badge != null && badgeStyle === "ribbon" ? (_jsx(PhiRibbonWrapControl, { text: badge, ...(config?.badgeColor ? { color: config.badgeColor } : {}), interactive: renderText != null, className: hoverEffect === "lift" ? "phi-card-ribbon phi-card-ribbon--lift" : "phi-card-ribbon", children: card })) : card;
|
|
312
355
|
}
|