@godxjp/ui 18.12.19 → 18.13.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/components/data-display/data-table.d.ts +8 -1
- package/dist/components/data-display/data-table.js +18 -5
- package/dist/components/data-display/descriptions.d.ts +1 -1
- package/dist/components/data-display/descriptions.js +1 -1
- package/dist/components/data-entry/form-field.js +29 -15
- package/dist/components/data-entry/form.d.ts +8 -0
- package/dist/components/data-entry/form.js +15 -0
- package/dist/components/layout/app-shell.d.ts +1 -1
- package/dist/components/layout/app-shell.js +42 -38
- package/dist/components/layout/error-surface.d.ts +3 -3
- package/dist/components/layout/error-surface.js +2 -1
- package/dist/components/navigation/app-setting-picker.js +1 -1
- package/dist/email/geometry.d.ts +6 -5
- package/dist/email/tokens.generated.d.ts +2 -2
- package/dist/email/tokens.generated.js +2 -2
- package/dist/props/components/data-entry.prop.d.ts +6 -0
- package/dist/props/components/layout.prop.d.ts +22 -5
- package/dist/props/registry.d.ts +2 -2
- package/dist/props/registry.js +2 -2
- package/dist/props/vocabulary/data.prop.d.ts +8 -0
- package/dist/props/vocabulary/layout.prop.d.ts +10 -5
- package/dist/styles/base.css +1 -1
- package/dist/styles/fonts.css +9 -9
- package/dist/styles/layout.css +16 -1
- package/dist/styles/shell-layout.css +24 -1
- package/dist/tokens/components/email.css +9 -7
- package/dist/tokens/components/form.css +7 -0
- package/dist/tokens/components/shell.css +7 -0
- package/dist/tokens/foundation.css +12 -3
- package/package.json +2 -2
|
@@ -116,8 +116,15 @@ export declare namespace DataTable {
|
|
|
116
116
|
placeholder?: string;
|
|
117
117
|
className?: string;
|
|
118
118
|
}) => React.JSX.Element;
|
|
119
|
-
var ViewOptions: ({ className }: {
|
|
119
|
+
var ViewOptions: ({ className, label, }: {
|
|
120
120
|
className?: string;
|
|
121
|
+
/**
|
|
122
|
+
* Overrides the button's text. The default ("View") is the shortest thing
|
|
123
|
+
* that fits a dense toolbar, but a product whose users know the control by a
|
|
124
|
+
* longer name — "View Options", "表示項目" — needs to say that, and hiding a
|
|
125
|
+
* bare text node from the outside is not something CSS can do.
|
|
126
|
+
*/
|
|
127
|
+
label?: React.ReactNode;
|
|
121
128
|
}) => React.JSX.Element | null;
|
|
122
129
|
var SelectAll: () => React.JSX.Element | null;
|
|
123
130
|
var BulkActions: ({ count, children, className, }: BulkActionsProps) => React.JSX.Element | null;
|
|
@@ -275,7 +275,10 @@ DataTable.Search = function DataTableSearch({
|
|
|
275
275
|
);
|
|
276
276
|
};
|
|
277
277
|
DataTable.Search.displayName = "DataTable.Search";
|
|
278
|
-
DataTable.ViewOptions = function DataTableViewOptions({
|
|
278
|
+
DataTable.ViewOptions = function DataTableViewOptions({
|
|
279
|
+
className,
|
|
280
|
+
label
|
|
281
|
+
}) {
|
|
279
282
|
const { table } = useDataTableContext();
|
|
280
283
|
const { t } = useTranslation();
|
|
281
284
|
const hideable = table.getAllLeafColumns().filter((c) => c.getCanHide());
|
|
@@ -283,7 +286,7 @@ DataTable.ViewOptions = function DataTableViewOptions({ className }) {
|
|
|
283
286
|
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
284
287
|
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className, children: [
|
|
285
288
|
/* @__PURE__ */ jsx(SlidersHorizontal, { className: "size-4 shrink-0", "aria-hidden": "true" }),
|
|
286
|
-
t("dataGrid.view")
|
|
289
|
+
label ?? t("dataGrid.view")
|
|
287
290
|
] }) }),
|
|
288
291
|
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
|
|
289
292
|
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: t("dataGrid.toggleColumns") }),
|
|
@@ -301,6 +304,13 @@ DataTable.ViewOptions = function DataTableViewOptions({ className }) {
|
|
|
301
304
|
] });
|
|
302
305
|
};
|
|
303
306
|
DataTable.ViewOptions.displayName = "DataTable.ViewOptions";
|
|
307
|
+
function columnWidth(width) {
|
|
308
|
+
if (width === void 0 || width === "") return {};
|
|
309
|
+
if (/^(?:[\d.]|calc\(|var\(|clamp\(|min\(|max\()/.test(width.trim())) {
|
|
310
|
+
return { style: { width } };
|
|
311
|
+
}
|
|
312
|
+
return { className: width };
|
|
313
|
+
}
|
|
304
314
|
function isEmptyHeader(header) {
|
|
305
315
|
return header == null || header === "" || header === false;
|
|
306
316
|
}
|
|
@@ -498,8 +508,9 @@ DataTable.Content = function DataTableContent() {
|
|
|
498
508
|
priority: col.priority,
|
|
499
509
|
"data-empty": headerEmpty || void 0,
|
|
500
510
|
"aria-sort": isSortable ? isActiveSort ? activeSort?.direction === "asc" ? "ascending" : "descending" : "none" : void 0,
|
|
511
|
+
style: columnWidth(col.width).style,
|
|
501
512
|
className: cn(
|
|
502
|
-
col.width,
|
|
513
|
+
columnWidth(col.width).className,
|
|
503
514
|
col.align === "right" && "text-end",
|
|
504
515
|
col.align === "center" && "text-center",
|
|
505
516
|
col.hiddenOnMobile && "hidden md:table-cell",
|
|
@@ -533,9 +544,10 @@ DataTable.Content = function DataTableContent() {
|
|
|
533
544
|
TableCell,
|
|
534
545
|
{
|
|
535
546
|
priority: col.priority,
|
|
547
|
+
style: columnWidth(col.width).style,
|
|
536
548
|
className: cn(
|
|
537
549
|
cellPadding,
|
|
538
|
-
col.width,
|
|
550
|
+
columnWidth(col.width).className,
|
|
539
551
|
col.align === "right" && "text-end",
|
|
540
552
|
col.align === "center" && "text-center",
|
|
541
553
|
col.hiddenOnMobile && "hidden md:table-cell",
|
|
@@ -655,9 +667,10 @@ DataTable.Content = function DataTableContent() {
|
|
|
655
667
|
TableCell,
|
|
656
668
|
{
|
|
657
669
|
priority: col.priority,
|
|
670
|
+
style: columnWidth(col.width).style,
|
|
658
671
|
className: cn(
|
|
659
672
|
cellPadding,
|
|
660
|
-
col.width,
|
|
673
|
+
columnWidth(col.width).className,
|
|
661
674
|
col.align === "right" && "text-end",
|
|
662
675
|
col.align === "center" && "text-center",
|
|
663
676
|
col.hiddenOnMobile && "hidden md:table-cell",
|
|
@@ -11,7 +11,7 @@ export interface DescriptionsProps {
|
|
|
11
11
|
* name/email block above an editable role field, for example) can be told to match it. Applies
|
|
12
12
|
* only in `layout="horizontal"` — a vertical label sits above the value and end-aligning it
|
|
13
13
|
* there would read as a mistake, exactly like `Form`'s own contract. Default `"start"`, matching
|
|
14
|
-
* this component's historical unconditional
|
|
14
|
+
* this component's historical unconditional start-align — no existing consumer's render changes.
|
|
15
15
|
*/
|
|
16
16
|
labelAlign?: "start" | "end";
|
|
17
17
|
className?: string;
|
|
@@ -49,7 +49,7 @@ Descriptions.Item = function DescriptionsItem({
|
|
|
49
49
|
className: cn(
|
|
50
50
|
"text-muted-foreground text-xs",
|
|
51
51
|
// `end`-align only ever applies in horizontal layout — same guard `Form` uses, so a
|
|
52
|
-
// vertical label (already above its value) never mistakenly
|
|
52
|
+
// vertical label (already above its value) never mistakenly end-aligns (gh#294).
|
|
53
53
|
layout === "horizontal" && labelAlign === "end" && "text-end"
|
|
54
54
|
),
|
|
55
55
|
children: label
|
|
@@ -58,8 +58,14 @@ function FormField({
|
|
|
58
58
|
"aria-label": childProps?.["aria-label"] ?? (typeof label === "string" ? label : void 0),
|
|
59
59
|
// Helper and error can coexist: helper stays on aria-describedby, the error on
|
|
60
60
|
// aria-errormessage (surfaced when aria-invalid is true).
|
|
61
|
-
"aria-describedby": mergeIds(
|
|
62
|
-
|
|
61
|
+
"aria-describedby": mergeIds(
|
|
62
|
+
childProps?.["aria-describedby"],
|
|
63
|
+
helperId
|
|
64
|
+
),
|
|
65
|
+
"aria-errormessage": mergeIds(
|
|
66
|
+
childProps?.["aria-errormessage"],
|
|
67
|
+
errorId
|
|
68
|
+
),
|
|
63
69
|
"aria-required": required ? true : childProps?.["aria-required"],
|
|
64
70
|
"aria-invalid": error ? true : childProps?.["aria-invalid"]
|
|
65
71
|
}) : children;
|
|
@@ -80,21 +86,29 @@ function FormField({
|
|
|
80
86
|
className: cn("ui-form-field", className),
|
|
81
87
|
children: [
|
|
82
88
|
/* @__PURE__ */ jsxs("div", { "data-slot": "form-field-label", className: "ui-form-field-label", children: [
|
|
83
|
-
/* @__PURE__ */ jsx(
|
|
84
|
-
|
|
89
|
+
/* @__PURE__ */ jsx(
|
|
90
|
+
Label,
|
|
85
91
|
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
asChild: true,
|
|
93
|
+
id: labelId,
|
|
94
|
+
className: "ui-inline-xs text-[length:var(--form-label-font-size)]",
|
|
95
|
+
children: /* @__PURE__ */ jsxs(
|
|
96
|
+
"span",
|
|
97
|
+
{
|
|
98
|
+
onClick: () => {
|
|
99
|
+
const el = document.getElementById(resolvedId);
|
|
100
|
+
if (!(el instanceof HTMLElement)) return;
|
|
101
|
+
const focusable = el.matches(FOCUSABLE_SELECTOR) ? el : el.querySelector(FOCUSABLE_SELECTOR);
|
|
102
|
+
(focusable ?? el).focus();
|
|
103
|
+
},
|
|
104
|
+
children: [
|
|
105
|
+
/* @__PURE__ */ jsx("span", { children: label }),
|
|
106
|
+
required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-destructive", children: "*" })
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
)
|
|
96
110
|
}
|
|
97
|
-
)
|
|
111
|
+
),
|
|
98
112
|
labelAddon
|
|
99
113
|
] }),
|
|
100
114
|
/* @__PURE__ */ jsxs("div", { "data-slot": "form-field-control", className: "ui-form-field-control", children: [
|
|
@@ -16,6 +16,13 @@ export declare function useFormLayout(): FormLayoutContextValue | null;
|
|
|
16
16
|
* label/control width, label alignment, and a responsive collapse breakpoint to every FormField
|
|
17
17
|
* inside it. A FormField may override any of these per-field. Optionally lays fields out in a
|
|
18
18
|
* responsive multi-column grid via `columns` (reuses ResponsiveGrid — mobile-first, 1 col on small).
|
|
19
|
+
*
|
|
20
|
+
* `asChild` renders the caller's own element instead of a `<form>`, keeping only the layout
|
|
21
|
+
* context. Routing libraries own the form element — Inertia's `<Form action method>` and
|
|
22
|
+
* TanStack Form both render their own — so without this every such consumer has to choose
|
|
23
|
+
* between the router's submission handling and the design system's field layout, and ends up
|
|
24
|
+
* hand-rolling label columns per field. Two `<form>` elements cannot nest, so wrapping is not
|
|
25
|
+
* an option: `<Form asChild layout="horizontal" labelWidth={174}><InertiaForm …/></Form>`.
|
|
19
26
|
*/
|
|
20
27
|
export declare const Form: React.ForwardRefExoticComponent<React.FormHTMLAttributes<HTMLFormElement> & {
|
|
21
28
|
layout?: FormLayoutProp;
|
|
@@ -25,5 +32,6 @@ export declare const Form: React.ForwardRefExoticComponent<React.FormHTMLAttribu
|
|
|
25
32
|
collapseBelow?: BreakpointProp | false;
|
|
26
33
|
columns?: import("../../props/components/layout.prop.js").ResponsiveGridColumnsProp;
|
|
27
34
|
density?: import("../../props/index.js").DensityProp;
|
|
35
|
+
asChild?: boolean;
|
|
28
36
|
className?: import("../../props/index.js").ClassNameProp;
|
|
29
37
|
} & React.RefAttributes<HTMLFormElement>>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import { cn } from "../../lib/utils.js";
|
|
5
6
|
import { ResponsiveGrid } from "../layout/responsive-grid.js";
|
|
@@ -15,6 +16,7 @@ const Form = React.forwardRef(function Form2({
|
|
|
15
16
|
collapseBelow = "md",
|
|
16
17
|
columns,
|
|
17
18
|
density,
|
|
19
|
+
asChild = false,
|
|
18
20
|
className,
|
|
19
21
|
children,
|
|
20
22
|
...props
|
|
@@ -24,6 +26,19 @@ const Form = React.forwardRef(function Form2({
|
|
|
24
26
|
[layout, labelWidth, controlWidth, labelAlign, collapseBelow]
|
|
25
27
|
);
|
|
26
28
|
const content = columns != null ? /* @__PURE__ */ jsx(ResponsiveGrid, { columns, children }) : children;
|
|
29
|
+
if (asChild) {
|
|
30
|
+
return /* @__PURE__ */ jsx(FormLayoutContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
31
|
+
Slot,
|
|
32
|
+
{
|
|
33
|
+
ref,
|
|
34
|
+
"data-slot": "form",
|
|
35
|
+
"data-layout": layout,
|
|
36
|
+
className: cn("ui-form", density && `ui-density-${density}`, className),
|
|
37
|
+
...props,
|
|
38
|
+
children
|
|
39
|
+
}
|
|
40
|
+
) });
|
|
41
|
+
}
|
|
27
42
|
return /* @__PURE__ */ jsx(
|
|
28
43
|
"form",
|
|
29
44
|
{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { AppShellProp } from "../../props/components/layout.prop.js";
|
|
3
3
|
export type { AppShellProp, AppShellProp as AppShellProps, } from "../../props/components/layout.prop.js";
|
|
4
|
-
export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, mobileNav, mobileNavLabel, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
|
|
4
|
+
export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, topbarSpan, mobileNav, mobileNavLabel, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
|
|
@@ -16,6 +16,7 @@ function AppShell({
|
|
|
16
16
|
children,
|
|
17
17
|
sidebarCollapsed = false,
|
|
18
18
|
responsiveNavigation = "drawer",
|
|
19
|
+
topbarSpan = "content",
|
|
19
20
|
mobileNav,
|
|
20
21
|
mobileNavLabel,
|
|
21
22
|
mobileNavOpen,
|
|
@@ -40,51 +41,54 @@ function AppShell({
|
|
|
40
41
|
/* @__PURE__ */ jsx("div", { className: "app-topbar-spacer" }),
|
|
41
42
|
topbarRight !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-right", children: topbarRight })
|
|
42
43
|
] });
|
|
44
|
+
const rail = /* @__PURE__ */ jsx("aside", { className: "app-sidebar", "aria-label": t("layout.appShell.sidebarLabel"), children: sidebar });
|
|
45
|
+
const bar = /* @__PURE__ */ jsxs("header", { className: "app-topbar ui-scale-fixed", "aria-label": t("layout.appShell.headerLabel"), children: [
|
|
46
|
+
hasDrawer && /* @__PURE__ */ jsxs(Sheet, { open: drawerOpen, onOpenChange: setDrawerOpen, children: [
|
|
47
|
+
/* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
48
|
+
Button,
|
|
49
|
+
{
|
|
50
|
+
type: "button",
|
|
51
|
+
variant: "ghost",
|
|
52
|
+
size: "sm",
|
|
53
|
+
className: "app-mobile-nav-trigger hidden max-[900px]:inline-flex",
|
|
54
|
+
"aria-label": t("layout.appShell.openNav"),
|
|
55
|
+
"aria-haspopup": "dialog",
|
|
56
|
+
children: /* @__PURE__ */ jsx(Menu, { className: "size-5", "aria-hidden": "true" })
|
|
57
|
+
}
|
|
58
|
+
) }),
|
|
59
|
+
/* @__PURE__ */ jsxs(
|
|
60
|
+
SheetContent,
|
|
61
|
+
{
|
|
62
|
+
side: "left",
|
|
63
|
+
width: "var(--app-shell-mobile-nav-width)",
|
|
64
|
+
className: "app-mobile-nav-drawer",
|
|
65
|
+
overlayClassName: "app-mobile-nav-overlay",
|
|
66
|
+
children: [
|
|
67
|
+
/* @__PURE__ */ jsx(SheetHeader, { title: mobileNavLabel ?? t("layout.appShell.navLabel") }),
|
|
68
|
+
/* @__PURE__ */ jsx(
|
|
69
|
+
SheetBody,
|
|
70
|
+
{
|
|
71
|
+
className: "app-mobile-nav-body px-[var(--app-shell-mobile-nav-inset)]",
|
|
72
|
+
onClick: handleDrawerClick,
|
|
73
|
+
children: drawerNav
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
] }),
|
|
80
|
+
resolvedTopbar
|
|
81
|
+
] });
|
|
43
82
|
return /* @__PURE__ */ jsxs(
|
|
44
83
|
"div",
|
|
45
84
|
{
|
|
46
85
|
className: "app-root",
|
|
47
86
|
"data-collapsed": sidebarCollapsed ? "true" : void 0,
|
|
48
87
|
"data-responsive-navigation": responsiveNavigation,
|
|
88
|
+
"data-topbar-span": topbarSpan === "full" ? "full" : void 0,
|
|
49
89
|
children: [
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
hasDrawer && /* @__PURE__ */ jsxs(Sheet, { open: drawerOpen, onOpenChange: setDrawerOpen, children: [
|
|
53
|
-
/* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
54
|
-
Button,
|
|
55
|
-
{
|
|
56
|
-
type: "button",
|
|
57
|
-
variant: "ghost",
|
|
58
|
-
size: "sm",
|
|
59
|
-
className: "app-mobile-nav-trigger hidden max-[900px]:inline-flex",
|
|
60
|
-
"aria-label": t("layout.appShell.openNav"),
|
|
61
|
-
"aria-haspopup": "dialog",
|
|
62
|
-
children: /* @__PURE__ */ jsx(Menu, { className: "size-5", "aria-hidden": "true" })
|
|
63
|
-
}
|
|
64
|
-
) }),
|
|
65
|
-
/* @__PURE__ */ jsxs(
|
|
66
|
-
SheetContent,
|
|
67
|
-
{
|
|
68
|
-
side: "left",
|
|
69
|
-
width: "var(--app-shell-mobile-nav-width)",
|
|
70
|
-
className: "app-mobile-nav-drawer",
|
|
71
|
-
overlayClassName: "app-mobile-nav-overlay",
|
|
72
|
-
children: [
|
|
73
|
-
/* @__PURE__ */ jsx(SheetHeader, { title: mobileNavLabel ?? t("layout.appShell.navLabel") }),
|
|
74
|
-
/* @__PURE__ */ jsx(
|
|
75
|
-
SheetBody,
|
|
76
|
-
{
|
|
77
|
-
className: "app-mobile-nav-body px-[var(--app-shell-mobile-nav-inset)]",
|
|
78
|
-
onClick: handleDrawerClick,
|
|
79
|
-
children: drawerNav
|
|
80
|
-
}
|
|
81
|
-
)
|
|
82
|
-
]
|
|
83
|
-
}
|
|
84
|
-
)
|
|
85
|
-
] }),
|
|
86
|
-
resolvedTopbar
|
|
87
|
-
] }),
|
|
90
|
+
topbarSpan === "full" ? bar : rail,
|
|
91
|
+
topbarSpan === "full" ? rail : bar,
|
|
88
92
|
/* @__PURE__ */ jsxs("main", { className: "app-main", "aria-label": t("layout.appShell.mainLabel"), tabIndex: 0, children: [
|
|
89
93
|
breadcrumb !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-breadcrumb", children: breadcrumb }),
|
|
90
94
|
children
|
|
@@ -3,8 +3,8 @@ import type { ErrorSurfaceProp } from "../../props/components/layout.prop.js";
|
|
|
3
3
|
export type { ErrorSurfaceMaintenanceProp, ErrorSurfaceProp, ErrorSurfaceProp as ErrorSurfaceProps, } from "../../props/components/layout.prop.js";
|
|
4
4
|
export type { ErrorSurfaceModeProp, ErrorSurfaceStatusProp } from "../../props/vocabulary/index.js";
|
|
5
5
|
/**
|
|
6
|
-
* ErrorSurface — the package-owned semantic exception surface for **403 · 404 · 500 · 503**
|
|
7
|
-
* (gh#221, gh#251).
|
|
6
|
+
* ErrorSurface — the package-owned semantic exception surface for **400 · 403 · 404 · 500 · 503**
|
|
7
|
+
* (gh#221, gh#251, gh#301).
|
|
8
8
|
*
|
|
9
9
|
* It exists because the alternative — every app composing `EmptyState` + `Flex` + `Text` inside a
|
|
10
10
|
* hand-picked shell — is unimportable: a consumer cannot `import` a docs page, so the four
|
|
@@ -12,7 +12,7 @@ export type { ErrorSurfaceModeProp, ErrorSurfaceStatusProp } from "../../props/v
|
|
|
12
12
|
* `.canonical-auth-card`, a bespoke `min-h-dvh` class). What the surface owns is the CONTRACT the
|
|
13
13
|
* composition kept losing:
|
|
14
14
|
*
|
|
15
|
-
* - **`mode` is the shell contract.** `application` (403/404) renders the surface as the BODY you
|
|
15
|
+
* - **`mode` is the shell contract.** `application` (400/403/404) renders the surface as the BODY you
|
|
16
16
|
* place inside the `AppShell` the route already provides, so the sidebar, topbar and breadcrumb
|
|
17
17
|
* are PRESERVED — it never reconstructs navigation chrome, because nav data and the user menu are
|
|
18
18
|
* consumer-owned and a component cannot manufacture them. `system` (500/503) owns the page and
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { SearchX, ServerCrash, ShieldAlert, Wrench } from "lucide-react";
|
|
4
|
+
import { SearchX, ServerCrash, ShieldAlert, TriangleAlert, Wrench } from "lucide-react";
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { EmptyState } from "../data-display/empty-state.js";
|
|
@@ -9,6 +9,7 @@ import { Progress } from "../data-display/progress.js";
|
|
|
9
9
|
import { Text } from "../general/typography.js";
|
|
10
10
|
import { CenteredShell } from "./centered-shell.js";
|
|
11
11
|
const STATUS_META = {
|
|
12
|
+
400: { icon: TriangleAlert, tone: "warning" },
|
|
12
13
|
403: { icon: ShieldAlert, tone: "warning" },
|
|
13
14
|
404: { icon: SearchX, tone: "muted" },
|
|
14
15
|
500: { icon: ServerCrash, tone: "destructive" },
|
|
@@ -149,7 +149,7 @@ const AppSettingPicker = React.forwardRef(
|
|
|
149
149
|
// (`data-[state=open]:border-ring`, from controlTriggerClass) and the
|
|
150
150
|
// focus-visible ring are untouched, so keyboard and "is this open" affordance
|
|
151
151
|
// still hold.
|
|
152
|
-
"ui-app-setting-picker-icon w-[length:var(--control-height)] justify-center border-transparent bg-transparent ps-0 pe-0 shadow-none
|
|
152
|
+
"ui-app-setting-picker-icon hover:bg-accent hover:text-accent-foreground w-[length:var(--control-height)] justify-center border-transparent bg-transparent ps-0 pe-0 shadow-none"
|
|
153
153
|
) : (
|
|
154
154
|
// Labeled: sized to a per-kind width from `sm` up; below `sm` it hugs its content and
|
|
155
155
|
// caps at the container (`w-auto max-w-full`) instead of the old UNCONDITIONAL
|
package/dist/email/geometry.d.ts
CHANGED
|
@@ -46,11 +46,12 @@ export declare const EMAIL_SHELL: EmailShellTokens;
|
|
|
46
46
|
/** Type ramp. Sizes are literal px — no email client resolves the rem scale reliably. */
|
|
47
47
|
export interface EmailTypographyTokens {
|
|
48
48
|
/**
|
|
49
|
-
* The canonical DXS stack,
|
|
50
|
-
* so this NAMES the face and degrades: a client with
|
|
51
|
-
* already loaded it) renders the canonical face;
|
|
52
|
-
* Yu Gothic (Windows) →
|
|
53
|
-
* names are SINGLE-quoted so the value drops
|
|
49
|
+
* The canonical DXS stack, Noto Sans JP first (product override, direct instruction). Web-font
|
|
50
|
+
* `@font-face` is unavailable in most clients, so this NAMES the face and degrades: a client with
|
|
51
|
+
* Noto Sans JP installed (or a webmail that already loaded it) renders the canonical face;
|
|
52
|
+
* everything else falls to Hiragino (macOS/iOS) → Yu Gothic (Windows) → M PLUS 2 → Meiryo → the
|
|
53
|
+
* system UI face → Arial → `sans-serif`. Family names are SINGLE-quoted so the value drops
|
|
54
|
+
* straight into a double-quoted `style="…"` attribute.
|
|
54
55
|
*/
|
|
55
56
|
readonly fontFamily: string;
|
|
56
57
|
/** Mono stack for invoice ids, masked card numbers, ISO dates and amounts. */
|
|
@@ -37,7 +37,7 @@ export declare const EMAIL_COLOR_SOURCE: {
|
|
|
37
37
|
};
|
|
38
38
|
readonly primary: {
|
|
39
39
|
readonly cssVar: "--primary";
|
|
40
|
-
readonly hsl: "204 100%
|
|
40
|
+
readonly hsl: "204 100% 37%";
|
|
41
41
|
};
|
|
42
42
|
readonly primaryForeground: {
|
|
43
43
|
readonly cssVar: "--primary-foreground";
|
|
@@ -138,7 +138,7 @@ export declare const EMAIL_GEOMETRY_SOURCE: {
|
|
|
138
138
|
readonly "--email-mark-gap": "8px";
|
|
139
139
|
readonly "--email-wordmark-font-size": "13px";
|
|
140
140
|
readonly "--email-wordmark-font-weight": "700";
|
|
141
|
-
readonly "--email-font-family-sans": "'
|
|
141
|
+
readonly "--email-font-family-sans": "'Noto Sans JP', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Yu Gothic Medium', YuGothic, 'M PLUS 2', Meiryo, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, system-ui, Arial, sans-serif";
|
|
142
142
|
readonly "--email-font-family-mono": "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
|
|
143
143
|
readonly "--email-body-font-size": "14px";
|
|
144
144
|
readonly "--email-body-line-height": "1.9";
|
|
@@ -29,7 +29,7 @@ const EMAIL_COLOR_SOURCE = {
|
|
|
29
29
|
},
|
|
30
30
|
"primary": {
|
|
31
31
|
"cssVar": "--primary",
|
|
32
|
-
"hsl": "204 100%
|
|
32
|
+
"hsl": "204 100% 37%"
|
|
33
33
|
},
|
|
34
34
|
"primaryForeground": {
|
|
35
35
|
"cssVar": "--primary-foreground",
|
|
@@ -128,7 +128,7 @@ const EMAIL_GEOMETRY_SOURCE = {
|
|
|
128
128
|
"--email-mark-gap": "8px",
|
|
129
129
|
"--email-wordmark-font-size": "13px",
|
|
130
130
|
"--email-wordmark-font-weight": "700",
|
|
131
|
-
"--email-font-family-sans": "'
|
|
131
|
+
"--email-font-family-sans": "'Noto Sans JP', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Yu Gothic Medium', YuGothic, 'M PLUS 2', Meiryo, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, system-ui, Arial, sans-serif",
|
|
132
132
|
"--email-font-family-mono": "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
133
133
|
"--email-body-font-size": "14px",
|
|
134
134
|
"--email-body-line-height": "1.9",
|
|
@@ -88,6 +88,12 @@ export type FormProp = React.FormHTMLAttributes<HTMLFormElement> & {
|
|
|
88
88
|
collapseBelow?: BreakpointProp | false;
|
|
89
89
|
columns?: ResponsiveGridColumnsProp;
|
|
90
90
|
density?: DensityProp;
|
|
91
|
+
/**
|
|
92
|
+
* Render the caller's own element instead of a `<form>`, keeping only the layout context.
|
|
93
|
+
* For routing libraries that own the form element (Inertia, TanStack Form) — two `<form>`
|
|
94
|
+
* elements cannot nest. `columns` does not apply in this mode; wrap fields in ResponsiveGrid.
|
|
95
|
+
*/
|
|
96
|
+
asChild?: boolean;
|
|
91
97
|
className?: ClassNameProp;
|
|
92
98
|
};
|
|
93
99
|
/**
|
|
@@ -222,6 +222,20 @@ export type AppShellProp = {
|
|
|
222
222
|
* rail; it intentionally suppresses the redundant mobile drawer trigger.
|
|
223
223
|
*/
|
|
224
224
|
responsiveNavigation?: "drawer" | "docked";
|
|
225
|
+
/**
|
|
226
|
+
* Which columns the topbar spans.
|
|
227
|
+
*
|
|
228
|
+
* - `"content"` (default) starts the topbar beside the sidebar, so the rail runs the full height
|
|
229
|
+
* of the window and the bar sits over the content only. The admin-console arrangement.
|
|
230
|
+
* - `"full"` runs the topbar edge to edge across the top with the sidebar starting beneath it —
|
|
231
|
+
* the arrangement products use when the bar carries space-level chrome (global search, account,
|
|
232
|
+
* notifications) that outranks the current section rather than belonging to it.
|
|
233
|
+
*
|
|
234
|
+
* Not cosmetic: it changes what the bar reads as owning. `"full"` also renders the `<header>`
|
|
235
|
+
* before the `<aside>` so keyboard order follows the visual order — a grid area alone would put
|
|
236
|
+
* focus in the rail while the eye starts at the bar (WCAG 2.4.3).
|
|
237
|
+
*/
|
|
238
|
+
topbarSpan?: "content" | "full";
|
|
225
239
|
/**
|
|
226
240
|
* Navigation shown in the mobile drawer at the DXS 900px breakpoint, where the docked sidebar is
|
|
227
241
|
* hidden. AppShell OWNS the drawer: it renders a hamburger trigger in the topbar and a focus-
|
|
@@ -431,10 +445,10 @@ export type ErrorSurfaceMaintenanceProp = {
|
|
|
431
445
|
progress?: number;
|
|
432
446
|
};
|
|
433
447
|
/**
|
|
434
|
-
* @see ErrorSurface — the package-owned semantic exception surface for 403 / 404 / 500 / 503.
|
|
448
|
+
* @see ErrorSurface — the package-owned semantic exception surface for 400 / 403 / 404 / 500 / 503.
|
|
435
449
|
*
|
|
436
450
|
* The `mode` is the SHELL CONTRACT, not a skin:
|
|
437
|
-
* - `mode="application"` (403/404) renders the surface as the **body** you put inside the
|
|
451
|
+
* - `mode="application"` (400/403/404) renders the surface as the **body** you put inside the
|
|
438
452
|
* `AppShell` the route already provides (normally within a `PageContainer`). It deliberately does
|
|
439
453
|
* NOT reconstruct navigation chrome: the sidebar, topbar and user menu are consumer-owned data,
|
|
440
454
|
* so the surface preserves the shell it is placed in instead of manufacturing a fake one.
|
|
@@ -450,7 +464,7 @@ export type ErrorSurfaceMaintenanceProp = {
|
|
|
450
464
|
* `t()`); the surface owns only its own metadata labels, which it localizes itself.
|
|
451
465
|
*/
|
|
452
466
|
export type ErrorSurfaceProp = {
|
|
453
|
-
/** Where the surface lives — `application` = AppShell body (403/404), `system` = own page (500/503). */
|
|
467
|
+
/** Where the surface lives — `application` = AppShell body (400/403/404), `system` = own page (500/503). */
|
|
454
468
|
mode: ErrorSurfaceModeProp;
|
|
455
469
|
/** HTTP status presented. Drives the default `icon`, `tone` and the rendered status code. */
|
|
456
470
|
status: ErrorSurfaceStatusProp;
|
|
@@ -464,9 +478,12 @@ export type ErrorSurfaceProp = {
|
|
|
464
478
|
* development-time error.
|
|
465
479
|
*/
|
|
466
480
|
action: ActionProp;
|
|
467
|
-
/**
|
|
481
|
+
/**
|
|
482
|
+
* Override the status-derived icon (400 TriangleAlert · 403 ShieldAlert · 404 SearchX ·
|
|
483
|
+
* 500 ServerCrash · 503 Wrench).
|
|
484
|
+
*/
|
|
468
485
|
icon?: IconProp;
|
|
469
|
-
/** Override the status-derived tone (403/503 `warning` · 404 `muted` · 500 `destructive`). */
|
|
486
|
+
/** Override the status-derived tone (400/403/503 `warning` · 404 `muted` · 500 `destructive`). */
|
|
470
487
|
tone?: EmptyStateToneProp;
|
|
471
488
|
/**
|
|
472
489
|
* Semantic heading level of `title`. Defaults to `2` in `application` mode (a `PageContainer`
|
package/dist/props/registry.d.ts
CHANGED
|
@@ -217,12 +217,12 @@ export declare const VOCABULARY_REGISTRY: {
|
|
|
217
217
|
readonly ErrorSurfaceModeProp: {
|
|
218
218
|
readonly file: "vocabulary/layout.prop.ts";
|
|
219
219
|
readonly category: "layout";
|
|
220
|
-
readonly description: "ErrorSurface shell contract — application (403/404 body inside the route's existing AppShell) vs system (500/503 owns the page via CenteredShell align=center)";
|
|
220
|
+
readonly description: "ErrorSurface shell contract — application (400/403/404 body inside the route's existing AppShell) vs system (500/503 owns the page via CenteredShell align=center)";
|
|
221
221
|
};
|
|
222
222
|
readonly ErrorSurfaceStatusProp: {
|
|
223
223
|
readonly file: "vocabulary/layout.prop.ts";
|
|
224
224
|
readonly category: "layout";
|
|
225
|
-
readonly description: "HTTP status an ErrorSurface presents — 403 | 404 | 500 | 503 (numeric); drives the default icon, tone and mode";
|
|
225
|
+
readonly description: "HTTP status an ErrorSurface presents — 400 | 403 | 404 | 500 | 503 (numeric); drives the default icon, tone and mode";
|
|
226
226
|
};
|
|
227
227
|
readonly AuthShellPresetProp: {
|
|
228
228
|
readonly file: "vocabulary/layout.prop.ts";
|
package/dist/props/registry.js
CHANGED
|
@@ -215,12 +215,12 @@ const VOCABULARY_REGISTRY = {
|
|
|
215
215
|
ErrorSurfaceModeProp: {
|
|
216
216
|
file: "vocabulary/layout.prop.ts",
|
|
217
217
|
category: "layout",
|
|
218
|
-
description: "ErrorSurface shell contract \u2014 application (403/404 body inside the route's existing AppShell) vs system (500/503 owns the page via CenteredShell align=center)"
|
|
218
|
+
description: "ErrorSurface shell contract \u2014 application (400/403/404 body inside the route's existing AppShell) vs system (500/503 owns the page via CenteredShell align=center)"
|
|
219
219
|
},
|
|
220
220
|
ErrorSurfaceStatusProp: {
|
|
221
221
|
file: "vocabulary/layout.prop.ts",
|
|
222
222
|
category: "layout",
|
|
223
|
-
description: "HTTP status an ErrorSurface presents \u2014 403 | 404 | 500 | 503 (numeric); drives the default icon, tone and mode"
|
|
223
|
+
description: "HTTP status an ErrorSurface presents \u2014 400 | 403 | 404 | 500 | 503 (numeric); drives the default icon, tone and mode"
|
|
224
224
|
},
|
|
225
225
|
AuthShellPresetProp: {
|
|
226
226
|
file: "vocabulary/layout.prop.ts",
|
|
@@ -24,6 +24,14 @@ export type ColumnDefProp<T> = {
|
|
|
24
24
|
ariaLabel?: string;
|
|
25
25
|
render?: (row: T) => React.ReactNode;
|
|
26
26
|
sortable?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Column width: either a utility class (`"w-[300px]"`) or a CSS length
|
|
29
|
+
* (`"300px"`, `"20%"`, `"calc(50% - 1rem)"`). Both work — a length is
|
|
30
|
+
* applied inline, anything else is treated as a class.
|
|
31
|
+
*
|
|
32
|
+
* It used to be class-only, and a length silently became a dead class name,
|
|
33
|
+
* so the column fell back to auto layout with nothing to indicate why.
|
|
34
|
+
*/
|
|
27
35
|
width?: string;
|
|
28
36
|
align?: ColumnAlignProp;
|
|
29
37
|
hiddenOnMobile?: boolean;
|
|
@@ -32,12 +32,17 @@ export type CenteredShellAlignProp = "start" | "center";
|
|
|
32
32
|
*/
|
|
33
33
|
export type ErrorSurfaceModeProp = "application" | "system";
|
|
34
34
|
/**
|
|
35
|
-
* The HTTP status an ErrorSurface presents. Deliberately closed to the
|
|
36
|
-
* app ships (RFC 9110 §15.5.4 / §15.5.5 / §15.6.1 / §15.6.4); it drives the default icon,
|
|
37
|
-
* the recommended `mode`. A number, never a string — it is the numeric status, and it is
|
|
38
|
-
* with tabular figures.
|
|
35
|
+
* The HTTP status an ErrorSurface presents. Deliberately closed to the five exception pages every
|
|
36
|
+
* app ships (RFC 9110 §15.5.1 / §15.5.4 / §15.5.5 / §15.6.1 / §15.6.4); it drives the default icon,
|
|
37
|
+
* tone and the recommended `mode`. A number, never a string — it is the numeric status, and it is
|
|
38
|
+
* rendered with tabular figures.
|
|
39
|
+
*
|
|
40
|
+
* `400` (gh#301) is the malformed-request page: a route reached with parameters the server refuses
|
|
41
|
+
* to interpret (a bad id, a missing launch parameter, a hand-edited query string). It is an
|
|
42
|
+
* `application` failure like 403/404 — the app itself is healthy, so the shell stays and the
|
|
43
|
+
* viewer navigates away from it.
|
|
39
44
|
*/
|
|
40
|
-
export type ErrorSurfaceStatusProp = 403 | 404 | 500 | 503;
|
|
45
|
+
export type ErrorSurfaceStatusProp = 400 | 403 | 404 | 500 | 503;
|
|
41
46
|
/**
|
|
42
47
|
* AuthShell named flow preset — the page MEASURE contract for a canonical hosted-identity flow
|
|
43
48
|
* (card max-width plus the desktop and mobile page gutters), owned by component tokens.
|
package/dist/styles/base.css
CHANGED
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
* base sans. Consumers switch a locale's face by setting its slot, e.g.
|
|
62
62
|
* :root { --font-sans-ja: "Noto Sans JP", var(--font-sans-base); }
|
|
63
63
|
* without writing any [lang] selectors. The opt-in bundle styles/fonts fills
|
|
64
|
-
* these slots for its
|
|
64
|
+
* these slots for its Noto Sans JP + M PLUS 2 fallback. `--font-sans-base` is a
|
|
65
65
|
* separate token (not --font-family-sans) so the fallback can't self-reference. */
|
|
66
66
|
[lang="ja"] {
|
|
67
67
|
--font-family-sans: var(--font-sans-ja, var(--font-sans-base));
|
package/dist/styles/fonts.css
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Self-contained so consumers need no font config. The browser only downloads
|
|
5
5
|
* the subset files matching the rendered text:
|
|
6
|
-
* ·
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* · Noto Sans JP — the DXS DEFAULT face for Japanese and Latin, including the
|
|
7
|
+
* Vietnamese glyph coverage used by the product locales
|
|
8
|
+
* (product override, direct instruction).
|
|
9
|
+
* · M PLUS 2 — retained as the fallback after Noto Sans JP.
|
|
9
10
|
*
|
|
10
11
|
* IMPORT ORDER IS LOAD-BEARING — this file MUST be imported AFTER
|
|
11
12
|
* `@godxjp/ui/styles/base`:
|
|
@@ -32,15 +33,14 @@
|
|
|
32
33
|
|
|
33
34
|
/* Fill the font-token slots (see tokens/foundation.css + styles/base.css) with
|
|
34
35
|
* the bundled faces, so the all-in-one `@godxjp/ui/styles` entry stays
|
|
35
|
-
* zero-config:
|
|
36
|
-
*
|
|
36
|
+
* zero-config: Noto Sans JP is the DXS default face for every supported locale
|
|
37
|
+
* and M PLUS 2 remains its fallback. Consumers on the per-layer setup who
|
|
37
38
|
* supply their own faces simply DON'T import this file and set the slots. */
|
|
38
39
|
:root {
|
|
39
40
|
--font-sans-base:
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
sans-serif;
|
|
41
|
+
"Noto Sans JP", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic Medium", YuGothic,
|
|
42
|
+
"M PLUS 2", Meiryo, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;
|
|
43
43
|
--font-sans-vi:
|
|
44
|
-
"
|
|
44
|
+
"Noto Sans JP", "M PLUS 2", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui,
|
|
45
45
|
sans-serif;
|
|
46
46
|
}
|
package/dist/styles/layout.css
CHANGED
|
@@ -613,9 +613,24 @@
|
|
|
613
613
|
gap: var(--space-inline-lg);
|
|
614
614
|
}
|
|
615
615
|
|
|
616
|
+
/* `extra` sizes to its content but STAYS SHRINKABLE (gh#300). `width: auto` releases the
|
|
617
|
+
* base rule's full-width stretch so a one-button header hugs the end edge; the historical
|
|
618
|
+
* `flex-shrink: 0` next to it then froze the box at the action group's max-content width,
|
|
619
|
+
* which is unbounded — an admin header with 10–13 buttons asks for ~1594px inside a 1408px
|
|
620
|
+
* content column. Because the box could not shrink, its own `flex-wrap: wrap` never had a
|
|
621
|
+
* narrower width to wrap into, the whole deficit was charged to `.ui-page-header-heading`
|
|
622
|
+
* (`min-w-0`, so it collapses to 0), and the `<h1>` wrapped one CJK character per line while
|
|
623
|
+
* the action group still overflowed the page. The <=720px escape hatch above
|
|
624
|
+
* (`.ui-page-header-extra > .ui-flex { max-width: 100% }`) could not help either: a
|
|
625
|
+
* percentage resolved against a fit-content parent is circular.
|
|
626
|
+
*
|
|
627
|
+
* `flex-shrink: 1` + `min-inline-size: 0` keep the max-content base size (nothing moves on a
|
|
628
|
+
* header that already fits) but let the row charge the overflow to `extra` too, so the wrap
|
|
629
|
+
* it already declares finally engages and the title keeps a readable measure. */
|
|
616
630
|
.ui-page-header-extra {
|
|
617
631
|
width: auto;
|
|
618
|
-
flex-shrink:
|
|
632
|
+
flex-shrink: 1;
|
|
633
|
+
min-inline-size: 0;
|
|
619
634
|
justify-content: flex-end;
|
|
620
635
|
}
|
|
621
636
|
}
|
|
@@ -25,6 +25,20 @@
|
|
|
25
25
|
grid-template-columns: var(--app-shell-rail-width) minmax(0, 1fr);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/* topbarSpan="full" — the bar runs edge to edge and the rail starts beneath it, for products
|
|
29
|
+
* whose bar carries space-level chrome (global search, account, notifications) that outranks the
|
|
30
|
+
* current section instead of belonging to it. Only the row assignment changes: both track sizes
|
|
31
|
+
* stay token-owned, so --app-shell-sidebar-width and the collapsed rail still apply untouched.
|
|
32
|
+
* Below the 900px breakpoint the grid already collapses to a single column, which IS this
|
|
33
|
+
* arrangement, so no narrow override is needed here — only the docked mode, which rebuilds the
|
|
34
|
+
* two-track grid, needs restating (see the breakpoint block). */
|
|
35
|
+
.app-root[data-topbar-span="full"] {
|
|
36
|
+
grid-template-areas:
|
|
37
|
+
"topbar topbar"
|
|
38
|
+
"sidebar main"
|
|
39
|
+
"sidebar footer";
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
.app-sidebar {
|
|
29
43
|
grid-area: sidebar;
|
|
30
44
|
display: flex;
|
|
@@ -672,6 +686,15 @@
|
|
|
672
686
|
grid-template-columns: var(--app-shell-rail-width) minmax(0, 1fr);
|
|
673
687
|
}
|
|
674
688
|
|
|
689
|
+
/* Docked mode rebuilds the two-track grid at narrow widths, which would otherwise undo
|
|
690
|
+
* topbarSpan="full" exactly where the bar's space-level controls matter most. */
|
|
691
|
+
.app-root[data-responsive-navigation="docked"][data-topbar-span="full"] {
|
|
692
|
+
grid-template-areas:
|
|
693
|
+
"topbar topbar"
|
|
694
|
+
"sidebar main"
|
|
695
|
+
"sidebar footer";
|
|
696
|
+
}
|
|
697
|
+
|
|
675
698
|
.app-root[data-responsive-navigation="docked"] > .app-sidebar {
|
|
676
699
|
display: flex;
|
|
677
700
|
}
|
|
@@ -932,7 +955,7 @@
|
|
|
932
955
|
gap: var(--sidebar-nav-item-gap);
|
|
933
956
|
padding-inline: var(--sidebar-nav-item-padding-x);
|
|
934
957
|
border: 0;
|
|
935
|
-
border-radius:
|
|
958
|
+
border-radius: var(--sidebar-nav-item-radius);
|
|
936
959
|
background: transparent;
|
|
937
960
|
/* gh#228 — the row/label foreground is its own knob (default = the muted role, unchanged). The
|
|
938
961
|
* icon reads a SEPARATE knob on `.sb-icon` below, so a service can darken 16px nav icons
|
|
@@ -38,17 +38,19 @@
|
|
|
38
38
|
--email-wordmark-font-weight: 700; /* mirrors --logo-wordmark-font-weight */
|
|
39
39
|
|
|
40
40
|
/* ── Typography — literal px so no client has to resolve the rem scale ──────────────────── */
|
|
41
|
-
/* The DXS canonical face is
|
|
42
|
-
* @font-face, so the stack ships the face NAME first and
|
|
43
|
-
* to a generic — a client with
|
|
44
|
-
*
|
|
45
|
-
*
|
|
41
|
+
/* The DXS canonical face is Noto Sans JP (see src/styles/fonts.css; product override, direct
|
|
42
|
+
* instruction). Email clients honour NO @font-face, so the stack ships the face NAME first and
|
|
43
|
+
* degrades through the platform JP faces to a generic — a client with Noto Sans JP installed (or
|
|
44
|
+
* a webmail that already loaded it, which is common — it is a widely preinstalled/CDN-cached
|
|
45
|
+
* Google face, more likely to actually render than the niche M PLUS 2 ever was) renders the
|
|
46
|
+
* canonical face, everything else lands on Hiragino (macOS/iOS) → Yu Gothic (Windows) →
|
|
47
|
+
* M PLUS 2 → Meiryo → the system UI face → Arial → sans-serif. Family names are SINGLE-quoted
|
|
46
48
|
* on export (the generator normalises) because an inline `style="…"` attribute is double-quoted;
|
|
47
49
|
* only ASCII aliases are listed, since a non-ASCII family name does not survive every transfer
|
|
48
50
|
* encoding. */
|
|
49
51
|
--email-font-family-sans:
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
+
"Noto Sans JP", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic Medium", YuGothic,
|
|
53
|
+
"M PLUS 2", Meiryo, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, Arial,
|
|
52
54
|
sans-serif;
|
|
53
55
|
/* Tabular/reference strings — invoice ids, masked card numbers, ISO dates, amounts — are set in
|
|
54
56
|
* the mono face in the canonical card. Mirrors --font-family-mono. */
|
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
/* Column gap between the label and its control in horizontal/inline layout. */
|
|
14
14
|
--form-label-gap: var(--space-4); /* 16px */
|
|
15
15
|
|
|
16
|
+
/* Type in the label column. Inherits the body size by default, so nothing moves unless a
|
|
17
|
+
* service opts in. It is a knob because the label column already is one: --form-label-width
|
|
18
|
+
* lets a service align forms to its grid, and a service whose grid was drawn around a
|
|
19
|
+
* smaller label had no way to say so — leaving it to hand-write font-size per label, which
|
|
20
|
+
* is exactly what the width token exists to prevent. */
|
|
21
|
+
--form-label-font-size: var(--text-sm); /* 14px */
|
|
22
|
+
|
|
16
23
|
/* Row rhythm between a Form's own top-level blocks (gh#295) — e.g. the field group's
|
|
17
24
|
* CardContent and its CardFooter action row, or two FormFields that happen to be Form's OWN
|
|
18
25
|
* direct children (no Card framing). Unchanged from the historical value. */
|
|
@@ -70,6 +70,13 @@
|
|
|
70
70
|
--sidebar-nav-item-gap: 0.625rem;
|
|
71
71
|
--sidebar-nav-item-padding-x: 0.625rem;
|
|
72
72
|
--sidebar-nav-gap: 2px;
|
|
73
|
+
/* Corner radius of a nav row. Defaults to the global rail radius, which is what an inset pill
|
|
74
|
+
* wants — but a rail that sets --sidebar-nav-scroll-padding and --sidebar-nav-gap to 0 makes its
|
|
75
|
+
* rows full-bleed bands flush with both edges, and a band wants square corners. Every other
|
|
76
|
+
* geometry property of `.sb-nav-item` was already a knob; this one was pinned to a constant, so
|
|
77
|
+
* the only way to square a band was a consumer selector against a DS internal class — the exact
|
|
78
|
+
* coupling rule #45 exists to prevent. Set `0` for full-bleed bands. */
|
|
79
|
+
--sidebar-nav-item-radius: calc(var(--radius) - 1px);
|
|
73
80
|
--sidebar-nav-scroll-padding: var(--space-3) var(--space-2);
|
|
74
81
|
--sidebar-section-gap: var(--space-4);
|
|
75
82
|
--sidebar-section-label-padding-x: var(--space-2);
|
|
@@ -9,7 +9,16 @@
|
|
|
9
9
|
--card-foreground: 48 8% 13%;
|
|
10
10
|
--popover: 60 33% 99%;
|
|
11
11
|
--popover-foreground: 48 8% 13%;
|
|
12
|
-
|
|
12
|
+
/* SmartHR MAIN, two lightness steps down from #0077c7 (gh#299).
|
|
13
|
+
*
|
|
14
|
+
* The published blue measures 4.62:1 as TEXT on --background — compliant,
|
|
15
|
+
* but with 0.12 of headroom, which every tint this system applies to a row
|
|
16
|
+
* then spends: the zebra stripe (muted/0.4) takes it to 4.46 and hover
|
|
17
|
+
* (muted/0.5) to 4.43. A link inside a striped table therefore failed AA
|
|
18
|
+
* while the same link on a card passed, which is not a defensible place to
|
|
19
|
+
* draw that line. 37% clears every one of those states with margin and is
|
|
20
|
+
* indistinguishable from the original side by side. */
|
|
21
|
+
--primary: 204 100% 37%; /* SmartHR MAIN #0071bd */
|
|
13
22
|
--primary-foreground: 60 33% 99%;
|
|
14
23
|
--secondary: 45 15% 95%; /* #f4f3f0 */
|
|
15
24
|
--secondary-foreground: 48 8% 13%;
|
|
@@ -200,8 +209,8 @@
|
|
|
200
209
|
* --font-sans-zh-hant (lang="zh-Hant" | "zh-TW")
|
|
201
210
|
* e.g. `--font-sans-ja: "Noto Sans JP", var(--font-sans-base);`
|
|
202
211
|
* The opt-in bundle `@godxjp/ui/styles/fonts` overwrites `--font-sans-base`
|
|
203
|
-
* (and `--font-sans-vi`) with the DXS bundle — "
|
|
204
|
-
* as
|
|
212
|
+
* (and `--font-sans-vi`) with the DXS bundle — "Noto Sans JP" primary, "M PLUS 2"
|
|
213
|
+
* as its fallback — so the all-in-one entry is zero-config. That fill also
|
|
205
214
|
* lands on `:root` at THIS specificity, so `styles/fonts` MUST be imported AFTER
|
|
206
215
|
* `styles/base`, or the system stack below silently wins (issue #210). */
|
|
207
216
|
--font-sans-base:
|