@godxjp/ui 33.22.1 → 33.24.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/DESIGN.md +1 -1
- package/README.md +4 -4
- package/agent/DESIGN.md +1 -1
- package/agent/START-HERE.md +7 -7
- package/agent/components/AgentBadge.json +43 -0
- package/agent/components/AppShell.json +6 -0
- package/agent/components/Avatar.json +7 -0
- package/agent/components/PageContainer.json +5 -0
- package/agent/components/PageErrorBoundary.json +48 -0
- package/agent/components/Toaster.json +1 -1
- package/agent/components/Toolbar.json +1 -1
- package/agent/components/Topbar.json +1 -1
- package/agent/components-index.json +10 -0
- package/agent/components.json +112 -3
- package/agent/index.json +7 -7
- package/agent/llms.txt +6 -6
- package/agent/tokens.json +84 -0
- package/dist/components/data-display/avatar.d.ts +1 -1
- package/dist/components/data-display/avatar.js +7 -1
- package/dist/components/data-display/badge.d.ts +11 -0
- package/dist/components/data-display/badge.js +20 -0
- package/dist/components/data-display/image.js +1 -1
- package/dist/components/data-display/index.d.ts +2 -2
- package/dist/components/data-display/index.js +2 -1
- package/dist/components/data-entry/record-picker.js +122 -98
- package/dist/components/data-entry/select.js +13 -3
- package/dist/components/layout/app-shell.d.ts +1 -1
- package/dist/components/layout/app-shell.js +3 -1
- package/dist/components/layout/index.d.ts +2 -0
- package/dist/components/layout/index.js +2 -0
- package/dist/components/layout/page-container.d.ts +1 -1
- package/dist/components/layout/page-container.js +6 -3
- package/dist/components/layout/page-error-boundary.d.ts +32 -0
- package/dist/components/layout/page-error-boundary.js +57 -0
- package/dist/components/layout/sidebar.d.ts +1 -1
- package/dist/components/layout/sidebar.js +100 -49
- package/dist/components/layout/topbar.js +108 -21
- package/dist/components/navigation/toolbar.js +2 -1
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +6 -0
- package/dist/i18n/messages/ja.json +6 -0
- package/dist/i18n/messages/vi.json +6 -0
- package/dist/props/components/data-display.prop.d.ts +27 -0
- package/dist/props/components/layout.prop.d.ts +45 -0
- package/dist/props/components/navigation.prop.d.ts +12 -1
- package/dist/props/registry.d.ts +50 -2
- package/dist/props/registry.js +72 -3
- package/dist/styles/alert-layout.css +4 -0
- package/dist/styles/badge-layout.css +14 -0
- package/dist/styles/data-display-layout.css +44 -0
- package/dist/styles/icon-layout.css +3 -0
- package/dist/styles/layers.json +22 -2
- package/dist/styles/layout.css +11 -0
- package/dist/styles/shell-layout.css +17 -1
- package/dist/styles/table-layout.css +1 -1
- package/dist/styles.css +1 -1
- package/dist/tokens/components/badge.css +3 -0
- package/dist/tokens/components/data-display.css +9 -0
- package/dist/tokens/components/shell.css +2 -0
- package/dist/tokens/foundation.css +8 -0
- package/docs/CONSUMER-RULES.md +1 -1
- package/package.json +3 -2
- package/scripts/consumer-rule.md +1 -1
- package/skills/godxjp-ui-navigation/SKILL.md +14 -9
- package/skills/godxjp-ui-navigation/references/principles.md +2 -2
package/agent/tokens.json
CHANGED
|
@@ -185,6 +185,24 @@
|
|
|
185
185
|
"tier": "foundation",
|
|
186
186
|
"value": "48 8% 13%"
|
|
187
187
|
},
|
|
188
|
+
{
|
|
189
|
+
"description": "AGENT role — marks an AI agent (Avatar kind=\"agent\", AgentBadge) so it is never mistaken for a person. Violet, chosen apart from every status hue so \"AI\" never reads as a state. FILL #7c3aed under white ink measures 5.70:1; TEXT --text-agent #6d28d9 on white measures 7.10:1 (AA). #7c3aed",
|
|
190
|
+
"name": "--agent",
|
|
191
|
+
"tier": "foundation",
|
|
192
|
+
"value": "262 83% 58%"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"description": "Foundation tokens: color, typography, raw spacing, ratio, radius, shadow.",
|
|
196
|
+
"name": "--agent-foreground",
|
|
197
|
+
"tier": "foundation",
|
|
198
|
+
"value": "0 0% 100%"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"description": "#6d28d9 — AA violet text on white",
|
|
202
|
+
"name": "--text-agent",
|
|
203
|
+
"tier": "foundation",
|
|
204
|
+
"value": "263 70% 50%"
|
|
205
|
+
},
|
|
188
206
|
{
|
|
189
207
|
"description": "IDENTITY role — the PRODUCT MARK colour (Logo `tone=\"success\"`, its wordmark, the email brand capsule). Neutral ink by default since v32 (gh#1220); the GoDX preset (`themes/godx.css`) sets it back to the violet of identity v2.3. It is deliberately distinct from two neighbours it keeps being confused with: · from --primary — a service re-theming its action colour must never recolour the mark. They hold the SAME VALUE by default and are still separate declarations; `brand-identity-role.test.ts` asserts on the DECLARATION, not the value, that this one never reads `var(--primary)` (gh#250). · from --success — 若竹 #68be8d is the STATUS green (badges, progress, \"saved\" text) and stays exactly as it is. The mark borrowed it once, rendering the identity at #69bf8e — ΔE76 ≈ 17.5 from the identity of the day, a plainly wrong brand colour (gh#250/#214). If the identity moves again, THIS COMMENT MOVES WITH IT. There is no Tailwind `bg-brand` utility on purpose: `--color-brand` is already taken by --text-brand, so identity surfaces read `hsl(var(--brand))` directly in component CSS. neutral ink. Same value as --primary by default, NOT an alias of it: re-theming the action colour must still leave the identity alone, which is what brand-identity-role.test.ts checks on the DECLARATION rather than the value.",
|
|
190
208
|
"name": "--brand",
|
|
@@ -2309,6 +2327,18 @@
|
|
|
2309
2327
|
"tier": "component",
|
|
2310
2328
|
"value": "initial"
|
|
2311
2329
|
},
|
|
2330
|
+
{
|
|
2331
|
+
"description": "AgentBadge — the violet outline and ink of the \"AI agent\" label. Role-mirrors, `initial`: hsl(var(--agent)) border and hsl(var(--text-agent)) ink at the call site.",
|
|
2332
|
+
"name": "--badge-agent-border",
|
|
2333
|
+
"tier": "component",
|
|
2334
|
+
"value": "initial"
|
|
2335
|
+
},
|
|
2336
|
+
{
|
|
2337
|
+
"description": "Badge component tokens.",
|
|
2338
|
+
"name": "--badge-agent-foreground",
|
|
2339
|
+
"tier": "component",
|
|
2340
|
+
"value": "initial"
|
|
2341
|
+
},
|
|
2312
2342
|
{
|
|
2313
2343
|
"description": "Leading/trailing glyph inside the pill. It was a bare `0.75rem` in badge-layout.css with no token at all, so a service could only resize it with `!important` or a forked stylesheet — the two routes the icon axis's tier 2 exists to abolish (gh#326). Same step, so nothing moves. Matches --badge-font-size's step by design: the glyph reads as a character in the label's run, so the two retune together. Deliberately NOT --scaling-multiplied — the literal it replaces did not track density.",
|
|
2314
2344
|
"name": "--badge-icon-size",
|
|
@@ -5939,6 +5969,54 @@
|
|
|
5939
5969
|
"tier": "component",
|
|
5940
5970
|
"value": "initial"
|
|
5941
5971
|
},
|
|
5972
|
+
{
|
|
5973
|
+
"description": "Avatar `kind=\"agent\"` — the \"AI\" tag pinned to the block-start/inline-end corner (presence owns block-end). The tag's text is aria-hidden; a localized sr-only \"AI agent\" carries the meaning, so the colour is never the only signal (WCAG 1.4.1). --avatar-agent-background / -foreground / -ring-color are ROLE-MIRROR knobs, `initial` here so the --agent / --agent-foreground / --background defaults resolve at the call site under a scoped [data-tenant] / .dark theme. Defaults = 2xs bold label · pill · violet --agent fill, white ink · a 2px --background separator ring · spilling --space-1 past the corner. default = var(--font-size-2xs) at the call site",
|
|
5974
|
+
"name": "--avatar-agent-font-size",
|
|
5975
|
+
"tier": "component",
|
|
5976
|
+
"value": "initial"
|
|
5977
|
+
},
|
|
5978
|
+
{
|
|
5979
|
+
"description": "default = var(--font-weight-bold) at the call site",
|
|
5980
|
+
"name": "--avatar-agent-font-weight",
|
|
5981
|
+
"tier": "component",
|
|
5982
|
+
"value": "initial"
|
|
5983
|
+
},
|
|
5984
|
+
{
|
|
5985
|
+
"description": "default = var(--space-1) at the call site",
|
|
5986
|
+
"name": "--avatar-agent-padding-inline",
|
|
5987
|
+
"tier": "component",
|
|
5988
|
+
"value": "initial"
|
|
5989
|
+
},
|
|
5990
|
+
{
|
|
5991
|
+
"description": "default = calc(var(--space-1) * -1) at the call site: the tag spills slightly past the mark",
|
|
5992
|
+
"name": "--avatar-agent-inset",
|
|
5993
|
+
"tier": "component",
|
|
5994
|
+
"value": "initial"
|
|
5995
|
+
},
|
|
5996
|
+
{
|
|
5997
|
+
"description": "default = var(--stroke-md) at the call site",
|
|
5998
|
+
"name": "--avatar-agent-ring-width",
|
|
5999
|
+
"tier": "component",
|
|
6000
|
+
"value": "initial"
|
|
6001
|
+
},
|
|
6002
|
+
{
|
|
6003
|
+
"description": "Data-display component tokens — small-by-design text knobs (rule #45/#46).",
|
|
6004
|
+
"name": "--avatar-agent-background",
|
|
6005
|
+
"tier": "component",
|
|
6006
|
+
"value": "initial"
|
|
6007
|
+
},
|
|
6008
|
+
{
|
|
6009
|
+
"description": "Data-display component tokens — small-by-design text knobs (rule #45/#46).",
|
|
6010
|
+
"name": "--avatar-agent-foreground",
|
|
6011
|
+
"tier": "component",
|
|
6012
|
+
"value": "initial"
|
|
6013
|
+
},
|
|
6014
|
+
{
|
|
6015
|
+
"description": "Data-display component tokens — small-by-design text knobs (rule #45/#46).",
|
|
6016
|
+
"name": "--avatar-agent-ring-color",
|
|
6017
|
+
"tier": "component",
|
|
6018
|
+
"value": "initial"
|
|
6019
|
+
},
|
|
5942
6020
|
{
|
|
5943
6021
|
"description": "Progress track + fill — `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, the default (`primary`) fill reads the brand mark; a service re-tones once. Defaults = hsl(var(--secondary)) track · hsl(var(--mark-primary, var(--primary))) fill (gh#1308 — it was the success mark; status tones are opt-in and do not read this knob).",
|
|
5944
6022
|
"name": "--progress-track-background",
|
|
@@ -16247,6 +16325,12 @@
|
|
|
16247
16325
|
"tier": "component",
|
|
16248
16326
|
"value": "var(--space-3)"
|
|
16249
16327
|
},
|
|
16328
|
+
{
|
|
16329
|
+
"description": "The gap between the brand and the custom bar inside `.app-topbar-rail` (gh#1481). It was a literal `--space-3`, so a theme that tightens the bar for a 320px / 200%-text viewport could reach every other gap in the row but this one.",
|
|
16330
|
+
"name": "--app-shell-rail-gap",
|
|
16331
|
+
"tier": "component",
|
|
16332
|
+
"value": "var(--space-3)"
|
|
16333
|
+
},
|
|
16250
16334
|
{
|
|
16251
16335
|
"description": "HOW MUCH OF THE BAR THE BRAND MAY KEEP (gh#728). A wide bar is not the problem, so the cap is `none` there; below the `sm` step the brand keeps at most a square of the row it sits in, and everything past that is cropped from the inline-end — which is the half a lockup can lose, because a lockup leads with its mark in both directions. A theme that wants the wordmark to survive on a phone raises the compact cap; a theme with a very wide lockup lowers the other. Choosing WHAT the narrow bar shows, rather than how much of it, is `AppShell logoCompact`: `viewBox` is an attribute and no stylesheet can set one.",
|
|
16252
16336
|
"name": "--app-shell-brand-max-inline-size",
|
|
@@ -3,7 +3,7 @@ import { type ImageLoadingStatus } from "../../lib/image-loading-status.js";
|
|
|
3
3
|
import type { AvatarImageFitProp, AvatarProp } from "../../props/components/data-display.prop.js";
|
|
4
4
|
export declare const Avatar: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
|
|
5
5
|
asChild?: boolean;
|
|
6
|
-
} & Pick<AvatarProp, "appearance" | "presence" | "presenceLabel" | "shape" | "size"> & React.RefAttributes<HTMLSpanElement>>;
|
|
6
|
+
} & Pick<AvatarProp, "appearance" | "kind" | "presence" | "presenceLabel" | "shape" | "size"> & React.RefAttributes<HTMLSpanElement>>;
|
|
7
7
|
export declare const AvatarImage: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "ref"> & {
|
|
8
8
|
asChild?: boolean;
|
|
9
9
|
onLoadingStatusChange?: (status: ImageLoadingStatus) => void;
|
|
@@ -22,6 +22,7 @@ const Avatar = React.forwardRef(
|
|
|
22
22
|
appearance = "default",
|
|
23
23
|
presence,
|
|
24
24
|
presenceLabel,
|
|
25
|
+
kind = "person",
|
|
25
26
|
asChild = false,
|
|
26
27
|
children,
|
|
27
28
|
...props
|
|
@@ -42,11 +43,16 @@ const Avatar = React.forwardRef(
|
|
|
42
43
|
"data-size": size === "md" ? void 0 : size,
|
|
43
44
|
"data-appearance": appearance === "tinted" ? "tinted" : void 0,
|
|
44
45
|
"data-presence": presence,
|
|
46
|
+
"data-kind": kind === "agent" ? "agent" : void 0,
|
|
45
47
|
className: cn("ui-avatar", className),
|
|
46
48
|
...props,
|
|
47
49
|
children: [
|
|
48
50
|
children,
|
|
49
|
-
presence !== void 0 ? /* @__PURE__ */ jsx("span", { "data-slot": "avatar-presence", "data-presence": presence, children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: presenceLabel ?? t(`dataDisplay.avatar.presence.${presence}`) }) }) : null
|
|
51
|
+
presence !== void 0 ? /* @__PURE__ */ jsx("span", { "data-slot": "avatar-presence", "data-presence": presence, children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: presenceLabel ?? t(`dataDisplay.avatar.presence.${presence}`) }) }) : null,
|
|
52
|
+
kind === "agent" ? /* @__PURE__ */ jsxs("span", { "data-slot": "avatar-agent", children: [
|
|
53
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "AI" }),
|
|
54
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t("dataDisplay.avatar.agent") })
|
|
55
|
+
] }) : null
|
|
50
56
|
]
|
|
51
57
|
}
|
|
52
58
|
) });
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type VariantProps } from "class-variance-authority";
|
|
3
|
+
import type { AgentBadgeProp } from "../../props/components/data-display.prop.js";
|
|
3
4
|
import type { ShapeProp, ToneProp } from "../../props/vocabulary/index.js";
|
|
4
5
|
export type BadgeVariant = "default" | "secondary" | "outline" | "dashed";
|
|
5
6
|
/**
|
|
@@ -100,4 +101,14 @@ export declare function Badge({ as: Element, className, variant, shape, tone, co
|
|
|
100
101
|
* general-purpose primitive.
|
|
101
102
|
*/
|
|
102
103
|
export declare const StatusBadge: typeof Badge;
|
|
104
|
+
/** @see AgentBadgeProp */
|
|
105
|
+
export type AgentBadgeProps = AgentBadgeProp;
|
|
106
|
+
/**
|
|
107
|
+
* The "AI agent" label beside a NAME — the text-row half of telling an AI agent apart from a
|
|
108
|
+
* person; `<Avatar kind="agent">` is the mark half. A list that shows names uses both; a place with
|
|
109
|
+
* only a mark (an assignee icon column, a dense cell) uses the Avatar alone. Violet outline on a
|
|
110
|
+
* transparent ground (`--badge-agent-*`, reading the `--agent` role), pill, never wraps. The label
|
|
111
|
+
* is real text, so it is announced as written and never colour-only.
|
|
112
|
+
*/
|
|
113
|
+
export declare function AgentBadge({ as: Element, className, children, ...props }: AgentBadgeProps): React.JSX.Element;
|
|
103
114
|
export {};
|
|
@@ -193,7 +193,27 @@ function Badge({
|
|
|
193
193
|
] });
|
|
194
194
|
}
|
|
195
195
|
const StatusBadge = Badge;
|
|
196
|
+
function AgentBadge({
|
|
197
|
+
as: Element = "span",
|
|
198
|
+
className,
|
|
199
|
+
children,
|
|
200
|
+
...props
|
|
201
|
+
}) {
|
|
202
|
+
const { t } = useTranslation();
|
|
203
|
+
return /* @__PURE__ */ jsx(
|
|
204
|
+
Element,
|
|
205
|
+
{
|
|
206
|
+
"data-slot": "badge",
|
|
207
|
+
"data-agent": "",
|
|
208
|
+
"data-shape": "pill",
|
|
209
|
+
className: cn(badgeVariants({ variant: "tinted", shape: "pill" }), className),
|
|
210
|
+
...props,
|
|
211
|
+
children: /* @__PURE__ */ jsx("span", { "data-slot": "badge-label", children: children ?? t("dataDisplay.avatar.agent") })
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
}
|
|
196
215
|
export {
|
|
216
|
+
AgentBadge,
|
|
197
217
|
Badge,
|
|
198
218
|
StatusBadge
|
|
199
219
|
};
|
|
@@ -454,7 +454,7 @@ const ImageRoot = React.forwardRef(function Image({
|
|
|
454
454
|
const setImgRef = React.useCallback(
|
|
455
455
|
(node) => {
|
|
456
456
|
imgRef.current = node;
|
|
457
|
-
if (node?.complete
|
|
457
|
+
if (node?.complete) setStatus(node.naturalWidth > 0 ? "loaded" : "error");
|
|
458
458
|
if (typeof ref === "function") ref(node);
|
|
459
459
|
else if (ref) ref.current = node;
|
|
460
460
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { Persona } from "./persona.js";
|
|
2
2
|
export type { PersonaProp, PersonaProps } from "./persona.js";
|
|
3
|
-
export { Badge, StatusBadge } from "./badge.js";
|
|
4
|
-
export type { BadgeProps, BadgeTone } from "./badge.js";
|
|
3
|
+
export { AgentBadge, Badge, StatusBadge } from "./badge.js";
|
|
4
|
+
export type { AgentBadgeProps, BadgeProps, BadgeTone } from "./badge.js";
|
|
5
5
|
export { Legend } from "./legend.js";
|
|
6
6
|
export type { LegendItemProp, LegendProp, LegendProps } from "./legend.js";
|
|
7
7
|
export { Swatch } from "./swatch.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Persona } from "./persona.js";
|
|
2
|
-
import { Badge, StatusBadge } from "./badge.js";
|
|
2
|
+
import { AgentBadge, Badge, StatusBadge } from "./badge.js";
|
|
3
3
|
import { Legend } from "./legend.js";
|
|
4
4
|
import { Swatch } from "./swatch.js";
|
|
5
5
|
import { FeatureList } from "./feature-list.js";
|
|
@@ -60,6 +60,7 @@ export {
|
|
|
60
60
|
AccordionContent,
|
|
61
61
|
AccordionItem,
|
|
62
62
|
AccordionTrigger,
|
|
63
|
+
AgentBadge,
|
|
63
64
|
Avatar,
|
|
64
65
|
AvatarFallback,
|
|
65
66
|
AvatarImage,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { Command, CommandGroup } from "./command.js";
|
|
22
22
|
import { Input } from "./input.js";
|
|
23
23
|
import { isImeComposing } from "../../lib/ime.js";
|
|
24
|
+
import { FieldNameContext } from "../../lib/field-a11y.js";
|
|
24
25
|
import { Select } from "./select.js";
|
|
25
26
|
function selectLoadOptionsFor(loadOptions) {
|
|
26
27
|
if (!loadOptions) return void 0;
|
|
@@ -35,103 +36,53 @@ function selectLoadOptionsFor(loadOptions) {
|
|
|
35
36
|
return { options: r.options, hasMore: r.nextCursor !== void 0 };
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
|
-
const RecordPicker = React.forwardRef(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
(next)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (shape === "inline") {
|
|
82
|
-
return /* @__PURE__ */ jsx(
|
|
83
|
-
InlinePicker,
|
|
84
|
-
{
|
|
85
|
-
ref,
|
|
86
|
-
isMultiple,
|
|
87
|
-
selected,
|
|
88
|
-
commit,
|
|
89
|
-
staticOptions,
|
|
90
|
-
loadOptions,
|
|
91
|
-
filters,
|
|
92
|
-
selectedOptions,
|
|
93
|
-
emptyOption,
|
|
94
|
-
placeholder,
|
|
95
|
-
dialogTitle,
|
|
96
|
-
notFoundContent,
|
|
97
|
-
loadMoreLabel,
|
|
98
|
-
disabled,
|
|
99
|
-
size,
|
|
100
|
-
className,
|
|
101
|
-
"data-field": fieldName,
|
|
102
|
-
...props
|
|
103
|
-
}
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
const total = count ?? (options ? staticOptions.length : void 0);
|
|
107
|
-
const asDialog = total === void 0 ? Boolean(loadOptions) : total > threshold;
|
|
108
|
-
if (!asDialog) {
|
|
109
|
-
return /* @__PURE__ */ jsx(
|
|
110
|
-
Select,
|
|
111
|
-
{
|
|
112
|
-
mode: isMultiple ? "multiple" : void 0,
|
|
113
|
-
loadOptions: selectLoadOptions,
|
|
114
|
-
id: props.id,
|
|
115
|
-
"data-field": fieldName,
|
|
116
|
-
"aria-describedby": props["aria-describedby"],
|
|
117
|
-
labelInValue: false,
|
|
118
|
-
showSearch: true,
|
|
119
|
-
options: withEmptyOption(options, emptyOption),
|
|
120
|
-
value: isMultiple ? selected : selected[0] ?? void 0,
|
|
121
|
-
onValueChange: ((next) => commit(toArray(next))),
|
|
122
|
-
placeholder: placeholder ?? t("dataEntry.recordPicker.placeholder"),
|
|
123
|
-
notFoundContent,
|
|
124
|
-
disabled,
|
|
125
|
-
size,
|
|
126
|
-
className
|
|
127
|
-
}
|
|
128
|
-
);
|
|
129
|
-
}
|
|
39
|
+
const RecordPicker = React.forwardRef(function RecordPicker2({
|
|
40
|
+
mode = "single",
|
|
41
|
+
shape = "auto",
|
|
42
|
+
value,
|
|
43
|
+
defaultValue,
|
|
44
|
+
onValueChange,
|
|
45
|
+
options,
|
|
46
|
+
loadOptions,
|
|
47
|
+
count,
|
|
48
|
+
threshold = 10,
|
|
49
|
+
filters,
|
|
50
|
+
selectedOptions,
|
|
51
|
+
emptyOption,
|
|
52
|
+
placeholder,
|
|
53
|
+
dialogTitle,
|
|
54
|
+
notFoundContent,
|
|
55
|
+
loadMoreLabel,
|
|
56
|
+
disabled = false,
|
|
57
|
+
size,
|
|
58
|
+
className,
|
|
59
|
+
"data-field": fieldName,
|
|
60
|
+
...props
|
|
61
|
+
}, ref) {
|
|
62
|
+
const { t } = useTranslation();
|
|
63
|
+
const isMultiple = mode === "multiple";
|
|
64
|
+
const controlled = value !== void 0;
|
|
65
|
+
const [internal, setInternal] = React.useState(
|
|
66
|
+
() => toArray(defaultValue ?? (isMultiple ? [] : null))
|
|
67
|
+
);
|
|
68
|
+
const selected = controlled ? toArray(value) : internal;
|
|
69
|
+
const commit = React.useCallback(
|
|
70
|
+
(next) => {
|
|
71
|
+
if (!controlled) setInternal(next);
|
|
72
|
+
onValueChange?.(isMultiple ? next : next[0] ?? null);
|
|
73
|
+
},
|
|
74
|
+
[controlled, isMultiple, onValueChange]
|
|
75
|
+
);
|
|
76
|
+
const selectLoadOptions = React.useMemo(() => selectLoadOptionsFor(loadOptions), [loadOptions]);
|
|
77
|
+
const staticOptions = React.useMemo(
|
|
78
|
+
() => options ? normalizeSelectOptions(options) : [],
|
|
79
|
+
[options]
|
|
80
|
+
);
|
|
81
|
+
if (shape === "inline") {
|
|
130
82
|
return /* @__PURE__ */ jsx(
|
|
131
|
-
|
|
83
|
+
InlinePicker,
|
|
132
84
|
{
|
|
133
85
|
ref,
|
|
134
|
-
"data-field": fieldName,
|
|
135
86
|
isMultiple,
|
|
136
87
|
selected,
|
|
137
88
|
commit,
|
|
@@ -147,11 +98,59 @@ const RecordPicker = React.forwardRef(
|
|
|
147
98
|
disabled,
|
|
148
99
|
size,
|
|
149
100
|
className,
|
|
101
|
+
"data-field": fieldName,
|
|
150
102
|
...props
|
|
151
103
|
}
|
|
152
104
|
);
|
|
153
105
|
}
|
|
154
|
-
);
|
|
106
|
+
const total = count ?? (options ? staticOptions.length : void 0);
|
|
107
|
+
const asDialog = total === void 0 ? Boolean(loadOptions) : total > threshold;
|
|
108
|
+
if (!asDialog) {
|
|
109
|
+
return /* @__PURE__ */ jsx(
|
|
110
|
+
Select,
|
|
111
|
+
{
|
|
112
|
+
mode: isMultiple ? "multiple" : void 0,
|
|
113
|
+
loadOptions: selectLoadOptions,
|
|
114
|
+
id: props.id,
|
|
115
|
+
"data-field": fieldName,
|
|
116
|
+
"aria-describedby": props["aria-describedby"],
|
|
117
|
+
labelInValue: false,
|
|
118
|
+
showSearch: true,
|
|
119
|
+
options: withEmptyOption(options, emptyOption),
|
|
120
|
+
value: isMultiple ? selected : selected[0] ?? void 0,
|
|
121
|
+
onValueChange: ((next) => commit(toArray(next))),
|
|
122
|
+
placeholder: placeholder ?? t("dataEntry.recordPicker.placeholder"),
|
|
123
|
+
notFoundContent,
|
|
124
|
+
disabled,
|
|
125
|
+
size,
|
|
126
|
+
className
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return /* @__PURE__ */ jsx(
|
|
131
|
+
DialogPicker,
|
|
132
|
+
{
|
|
133
|
+
ref,
|
|
134
|
+
"data-field": fieldName,
|
|
135
|
+
isMultiple,
|
|
136
|
+
selected,
|
|
137
|
+
commit,
|
|
138
|
+
staticOptions,
|
|
139
|
+
loadOptions,
|
|
140
|
+
filters,
|
|
141
|
+
selectedOptions,
|
|
142
|
+
emptyOption,
|
|
143
|
+
placeholder,
|
|
144
|
+
dialogTitle,
|
|
145
|
+
notFoundContent,
|
|
146
|
+
loadMoreLabel,
|
|
147
|
+
disabled,
|
|
148
|
+
size,
|
|
149
|
+
className,
|
|
150
|
+
...props
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
});
|
|
155
154
|
const INLINE_SUGGESTION_LIMIT = 8;
|
|
156
155
|
function toArray(v) {
|
|
157
156
|
if (v === null || v === void 0) return [];
|
|
@@ -179,10 +178,30 @@ const InlinePicker = React.forwardRef(function InlinePicker2({
|
|
|
179
178
|
className,
|
|
180
179
|
id,
|
|
181
180
|
"data-field": fieldName,
|
|
182
|
-
"aria-describedby": describedBy
|
|
181
|
+
"aria-describedby": describedBy,
|
|
182
|
+
"aria-label": ariaLabel,
|
|
183
|
+
"aria-labelledby": ariaLabelledBy,
|
|
184
|
+
"aria-errormessage": ariaErrorMessage,
|
|
185
|
+
"aria-invalid": ariaInvalid,
|
|
186
|
+
"aria-required": ariaRequired
|
|
183
187
|
}, ref) {
|
|
184
188
|
const { t } = useTranslation();
|
|
185
189
|
const [query, setQuery] = React.useState("");
|
|
190
|
+
const inputRef = React.useRef(null);
|
|
191
|
+
const [hasNativeLabel, setHasNativeLabel] = React.useState(false);
|
|
192
|
+
React.useLayoutEffect(() => {
|
|
193
|
+
setHasNativeLabel((inputRef.current?.labels?.length ?? 0) > 0);
|
|
194
|
+
}, [id]);
|
|
195
|
+
const enclosingField = React.useContext(FieldNameContext);
|
|
196
|
+
const fallbackName = ariaLabel === void 0 && ariaLabelledBy === void 0 && !enclosingField && !hasNativeLabel ? placeholder ?? t("dataEntry.recordPicker.search") : void 0;
|
|
197
|
+
const setInputRef = React.useCallback(
|
|
198
|
+
(node) => {
|
|
199
|
+
inputRef.current = node;
|
|
200
|
+
if (typeof ref === "function") ref(node);
|
|
201
|
+
else if (ref) ref.current = node;
|
|
202
|
+
},
|
|
203
|
+
[ref]
|
|
204
|
+
);
|
|
186
205
|
const [rows, setRows] = React.useState(staticOptions);
|
|
187
206
|
const [openSuggest, setOpenSuggest] = React.useState(false);
|
|
188
207
|
const [dialogQuery, setDialogQuery] = React.useState(null);
|
|
@@ -214,7 +233,7 @@ const InlinePicker = React.forwardRef(function InlinePicker2({
|
|
|
214
233
|
/* @__PURE__ */ jsx(
|
|
215
234
|
Input,
|
|
216
235
|
{
|
|
217
|
-
ref,
|
|
236
|
+
ref: setInputRef,
|
|
218
237
|
type: "search",
|
|
219
238
|
value: query,
|
|
220
239
|
disabled,
|
|
@@ -232,7 +251,12 @@ const InlinePicker = React.forwardRef(function InlinePicker2({
|
|
|
232
251
|
className: "ui-record-picker-inline-input",
|
|
233
252
|
id,
|
|
234
253
|
"data-field": fieldName,
|
|
235
|
-
"aria-describedby": describedBy
|
|
254
|
+
"aria-describedby": describedBy,
|
|
255
|
+
"aria-label": ariaLabel ?? fallbackName,
|
|
256
|
+
"aria-labelledby": ariaLabelledBy,
|
|
257
|
+
"aria-errormessage": ariaErrorMessage,
|
|
258
|
+
"aria-invalid": ariaInvalid,
|
|
259
|
+
"aria-required": ariaRequired
|
|
236
260
|
}
|
|
237
261
|
),
|
|
238
262
|
/* @__PURE__ */ jsxs(
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
Select as AriaSelect,
|
|
14
14
|
SelectStateContext,
|
|
15
15
|
SelectValue as AriaSelectValue,
|
|
16
|
-
Separator as AriaSeparator
|
|
16
|
+
Separator as AriaSeparator,
|
|
17
|
+
Text as AriaText
|
|
17
18
|
} from "react-aria-components";
|
|
18
19
|
import { ChevronDown, Loader2, X } from "lucide-react";
|
|
19
20
|
import { cn } from "../../lib/utils.js";
|
|
@@ -613,6 +614,14 @@ function withOptionIcon(option, label) {
|
|
|
613
614
|
/* @__PURE__ */ jsx("span", { className: "ui-select-option-label", children: label })
|
|
614
615
|
] });
|
|
615
616
|
}
|
|
617
|
+
function optionRow(option) {
|
|
618
|
+
if (!option.sublabel) return withOptionIcon(option, option.label);
|
|
619
|
+
return /* @__PURE__ */ jsxs("span", { className: "ui-select-option-body", children: [
|
|
620
|
+
option.icon ? /* @__PURE__ */ jsx("span", { className: "ui-select-option-icon", "aria-hidden": "true", children: option.icon }) : null,
|
|
621
|
+
/* @__PURE__ */ jsx(AriaText, { slot: "label", className: "ui-select-option-label", children: option.label }),
|
|
622
|
+
/* @__PURE__ */ jsx(AriaText, { slot: "description", className: "ui-search-select-option-sublabel", children: option.sublabel })
|
|
623
|
+
] });
|
|
624
|
+
}
|
|
616
625
|
function DataSelectLabel({
|
|
617
626
|
labelRender,
|
|
618
627
|
options
|
|
@@ -729,7 +738,7 @@ function DataSelect(props) {
|
|
|
729
738
|
disabled: option.disabled,
|
|
730
739
|
"data-testid": optionTestId(option.value),
|
|
731
740
|
selectedIcon: menuItemSelectedIcon,
|
|
732
|
-
children: optionRender ? optionRender(option, { index }) : renderOption ? renderOption(option) :
|
|
741
|
+
children: optionRender ? optionRender(option, { index }) : renderOption ? renderOption(option) : optionRow(option)
|
|
733
742
|
},
|
|
734
743
|
option.value
|
|
735
744
|
);
|
|
@@ -784,8 +793,9 @@ function DataSelect(props) {
|
|
|
784
793
|
...ariaProps,
|
|
785
794
|
children: [
|
|
786
795
|
prefix === void 0 || prefix === null ? null : /* @__PURE__ */ jsx("span", { "data-slot": "select-prefix", className: "ui-select-prefix", children: prefix }),
|
|
787
|
-
/* @__PURE__ */ jsx(SelectValue, { placeholder: placeholder ?? t("dataEntry.searchSelect.placeholder"), children: labelRender ? /* @__PURE__ */ jsx(DataSelectLabel, { labelRender, options }) : options.some((option) => option.icon) ? (
|
|
796
|
+
/* @__PURE__ */ jsx(SelectValue, { placeholder: placeholder ?? t("dataEntry.searchSelect.placeholder"), children: labelRender ? /* @__PURE__ */ jsx(DataSelectLabel, { labelRender, options }) : options.some((option) => option.icon || option.sublabel) ? (
|
|
788
797
|
// gh#1444 — the selected option's icon leads the trigger too, as in the search panel.
|
|
798
|
+
// gh#1495 — and a sublabel stays in the row: the trigger names the pick by its label.
|
|
789
799
|
/* @__PURE__ */ jsx(
|
|
790
800
|
DataSelectLabel,
|
|
791
801
|
{
|
|
@@ -3,4 +3,4 @@ import type { AppShellProp } from "../../props/components/layout.prop.js";
|
|
|
3
3
|
export type { AppShellProp, AppShellProp as AppShellProps, } from "../../props/components/layout.prop.js";
|
|
4
4
|
/** Same rail boundary as shell-layout.css; SSR starts in docked mode. */
|
|
5
5
|
export declare function useAppShellNavigationMode(): "drawer" | "docked";
|
|
6
|
-
export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, logoCompact, logoCompactBelow, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, topbarSpan, navRail, navRailPosition, navRailEnd, navRailLabel, mobileTabBar, mobileTabBarLabel, mobileNav, mobileNavLabel, mobileNavTriggerLabel, mobileNavOnCloseAutoFocus, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
|
|
6
|
+
export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, logoCompact, logoCompactBelow, breadcrumb, footer, children, errorBoundary, sidebarCollapsed, responsiveNavigation, topbarSpan, navRail, navRailPosition, navRailEnd, navRailLabel, mobileTabBar, mobileTabBarLabel, mobileNav, mobileNavLabel, mobileNavTriggerLabel, mobileNavOnCloseAutoFocus, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
|
|
@@ -10,6 +10,7 @@ import { ShellTabBar } from "./mobile-shell.js";
|
|
|
10
10
|
import { useToastStickyBarClearance } from "../../lib/toast-clearance.js";
|
|
11
11
|
import { NavSurfaceProvider } from "./nav-surface.js";
|
|
12
12
|
import { TopbarItem } from "./topbar-item.js";
|
|
13
|
+
import { PageErrorBoundary } from "./page-error-boundary.js";
|
|
13
14
|
import { isDevelopment } from "../../lib/dev.js";
|
|
14
15
|
function useAppShellNavigationMode() {
|
|
15
16
|
return useMediaQuery("(width <= 56.25rem)") ? "drawer" : "docked";
|
|
@@ -25,6 +26,7 @@ function AppShell({
|
|
|
25
26
|
breadcrumb,
|
|
26
27
|
footer,
|
|
27
28
|
children,
|
|
29
|
+
errorBoundary = true,
|
|
28
30
|
sidebarCollapsed = false,
|
|
29
31
|
responsiveNavigation = "drawer",
|
|
30
32
|
topbarSpan = "content",
|
|
@@ -201,7 +203,7 @@ function AppShell({
|
|
|
201
203
|
tabIndex: 0,
|
|
202
204
|
children: [
|
|
203
205
|
breadcrumb !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-breadcrumb", children: breadcrumb }),
|
|
204
|
-
children,
|
|
206
|
+
errorBoundary === false ? children : /* @__PURE__ */ jsx(PageErrorBoundary, { ...errorBoundary === true ? {} : errorBoundary, children }),
|
|
205
207
|
showTabBar && footerRegion
|
|
206
208
|
]
|
|
207
209
|
}
|
|
@@ -31,6 +31,8 @@ export { CenteredShell } from "./centered-shell.js";
|
|
|
31
31
|
export type { CenteredShellProp, CenteredShellProps, CenteredShellAuthProp, CenteredShellPageProp, } from "./centered-shell.js";
|
|
32
32
|
export type { CenteredShellWidthProp, CenteredShellAlignProp } from "../../props/vocabulary/index.js";
|
|
33
33
|
export { ErrorSurface } from "./error-surface.js";
|
|
34
|
+
export { PageErrorBoundary } from "./page-error-boundary.js";
|
|
35
|
+
export type { PageErrorBoundaryFallbackProp, PageErrorBoundaryProp, PageErrorBoundaryProps, } from "./page-error-boundary.js";
|
|
34
36
|
export type { ErrorSurfaceMaintenanceProp, ErrorSurfaceProp, ErrorSurfaceProps, } from "./error-surface.js";
|
|
35
37
|
export type { ErrorSurfaceModeProp, ErrorSurfaceStatusProp } from "../../props/vocabulary/index.js";
|
|
36
38
|
export { Breadcrumb } from "./breadcrumb.js";
|
|
@@ -14,6 +14,7 @@ import { AuthStack } from "./auth-stack.js";
|
|
|
14
14
|
import { MobileShell } from "./mobile-shell.js";
|
|
15
15
|
import { CenteredShell } from "./centered-shell.js";
|
|
16
16
|
import { ErrorSurface } from "./error-surface.js";
|
|
17
|
+
import { PageErrorBoundary } from "./page-error-boundary.js";
|
|
17
18
|
import { Breadcrumb } from "./breadcrumb.js";
|
|
18
19
|
import { createSidebarLink, Sidebar, SidebarHeader, SidebarItem, SidebarSection } from "./sidebar.js";
|
|
19
20
|
import { Topbar } from "./topbar.js";
|
|
@@ -141,6 +142,7 @@ export {
|
|
|
141
142
|
NavList,
|
|
142
143
|
OrgSwitcher,
|
|
143
144
|
PageContainer,
|
|
145
|
+
PageErrorBoundary,
|
|
144
146
|
PriceSummary,
|
|
145
147
|
ResizableHandle,
|
|
146
148
|
ResizablePanel,
|
|
@@ -2,7 +2,7 @@ import type { PageContainerProp, PageInsetProp } from "../../props/components/la
|
|
|
2
2
|
export type { PageContainerProp, PageContainerProp as PageContainerProps, PageContainerExtraProp, } from "../../props/components/layout.prop.js";
|
|
3
3
|
export type { BreadcrumbItemProp, BreadcrumbItemProp as BreadcrumbItem, } from "../../props/vocabulary/navigation.prop.js";
|
|
4
4
|
export declare function PageContainerInset({ className, children, ...props }: PageInsetProp): import("react").JSX.Element;
|
|
5
|
-
declare function PageContainerRoot({ title, subtitle, status, extra, toolbar, footer, toolbarPad, footerPad, breadcrumb, breadcrumbCurrentItem, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, titleRef, density, variant, preset, headerLayout, headerScale, icon, cover, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
|
|
5
|
+
declare function PageContainerRoot({ title, subtitle, status, extra, help, toolbar, footer, toolbarPad, footerPad, breadcrumb, breadcrumbCurrentItem, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, titleRef, density, variant, preset, headerLayout, headerScale, icon, cover, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
|
|
6
6
|
export declare const PageContainer: typeof PageContainerRoot & {
|
|
7
7
|
Inset: typeof PageContainerInset;
|
|
8
8
|
};
|
|
@@ -37,6 +37,7 @@ function PageContainerRoot({
|
|
|
37
37
|
subtitle,
|
|
38
38
|
status,
|
|
39
39
|
extra,
|
|
40
|
+
help,
|
|
40
41
|
toolbar,
|
|
41
42
|
footer,
|
|
42
43
|
toolbarPad,
|
|
@@ -68,7 +69,8 @@ function PageContainerRoot({
|
|
|
68
69
|
const hasHeading = headerLoading || hasTitle || hasIcon || status != null || !!subtitle;
|
|
69
70
|
const hasBreadcrumb = !!breadcrumb && breadcrumb.length > 0;
|
|
70
71
|
const hasEyecatch = isValidElement(cover) && cover.props.variant === "eyecatch";
|
|
71
|
-
const
|
|
72
|
+
const hasHelp = help != null && help !== false;
|
|
73
|
+
const hasHeader = hasHeading || hasBreadcrumb || hasEyecatch || extraStart != null || extraEnd != null || hasHelp;
|
|
72
74
|
const reveal = stickyFooter && footer != null && footerReveal === "onScroll" && hasHeader;
|
|
73
75
|
const { headerRef, revealed } = useFooterReveal(reveal);
|
|
74
76
|
const footerRef = useRef(null);
|
|
@@ -177,9 +179,10 @@ function PageContainerRoot({
|
|
|
177
179
|
)
|
|
178
180
|
) : subtitle && /* @__PURE__ */ jsx("p", { className: "ui-page-subtitle", children: subtitle })
|
|
179
181
|
] }),
|
|
180
|
-
(extraStart != null || extraEnd != null) && /* @__PURE__ */ jsxs("div", { className: "ui-page-header-extra", children: [
|
|
182
|
+
(extraStart != null || extraEnd != null || hasHelp) && /* @__PURE__ */ jsxs("div", { className: "ui-page-header-extra", children: [
|
|
181
183
|
extraStart,
|
|
182
|
-
extraEnd
|
|
184
|
+
extraEnd,
|
|
185
|
+
hasHelp ? /* @__PURE__ */ jsx("div", { className: "ui-page-header-help", "data-slot": "page-header-help", children: help }) : null
|
|
183
186
|
] })
|
|
184
187
|
] })
|
|
185
188
|
]
|