@godxjp/ui 23.4.2 → 23.4.3
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/README.md +17 -2
- package/dist/components/data-entry/date-picker.js +4 -16
- package/dist/components/layout/draggable-panel.d.ts +3 -2
- package/dist/components/layout/draggable-panel.js +11 -2
- package/dist/components/layout/index.d.ts +1 -1
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/layout.prop.d.ts +13 -2
- package/dist/props/registry.d.ts +4 -0
- package/dist/props/registry.js +5 -0
- package/dist/styles/control.css +21 -0
- package/dist/styles/core-with-fallbacks.css +3 -0
- package/dist/styles/font-fallbacks.css +83 -0
- package/dist/styles/fonts.css +1 -82
- package/dist/styles/layout.css +5 -0
- package/dist/tokens/components/draggable-panel.css +4 -0
- package/docs/CONSUMER-RULES.md +1 -1
- package/docs/CUSTOMER-THEMING.md +16 -2
- package/docs/DESIGN-AUTHORITY.md +4 -0
- package/package.json +4 -2
- package/scripts/_agent-setup.mjs +28 -3
- package/scripts/cli.mjs +6 -2
- package/scripts/consumer-rule.md +76 -18
- package/scripts/guinea-pig-skill.md +38 -9
- package/scripts/ui-audit.mjs +1 -1
package/README.md
CHANGED
|
@@ -110,11 +110,26 @@ The framework ships colors, the type scale, the wa-iro palette, and (opt-in) bun
|
|
|
110
110
|
@import "@godxjp/ui/styles/core"; /* every component layer, no @font-face */
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
Supplying Noto Sans JP yourself and want the cold-visit swap not to reflow the page? Take the
|
|
114
|
+
third entry instead — the same layers plus the six metric-matched fallback faces, whose `src` is
|
|
115
|
+
`local()`-only, so it costs **zero network bytes** over `core`:
|
|
116
|
+
|
|
117
|
+
```css
|
|
118
|
+
@import "@godxjp/ui/styles/core-with-fallbacks"; /* core + 6 local()-only faces */
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Then name the family directly after your own face:
|
|
122
|
+
`--font-sans-base: "Noto Sans JP", "Noto Sans JP Fallback", system-ui, sans-serif;`
|
|
123
|
+
|
|
124
|
+
`core` stays at **zero** `@font-face` on purpose — `grep -c '@font-face'
|
|
125
|
+
node_modules/@godxjp/ui/dist/styles/core.css` → `0` is the promise, so the fallbacks got their own
|
|
126
|
+
entry rather than being folded in.
|
|
127
|
+
|
|
113
128
|
> **Do not cherry-pick `*-layout.css` files.** Layers depend on each other (a
|
|
114
129
|
> Select's rows, a menu's surface, a form's rhythm live in shared rules) and a
|
|
115
130
|
> missing layer fails silently: menus render with no background, rows with no
|
|
116
|
-
> height. `styles` and `styles/core` are the
|
|
117
|
-
> `visual-audit` flags a page whose layers are incomplete (`css-layers-missing`).
|
|
131
|
+
> height. `styles`, `styles/core` and `styles/core-with-fallbacks` are the three supported entries;
|
|
132
|
+
> the runtime `visual-audit` flags a page whose layers are incomplete (`css-layers-missing`).
|
|
118
133
|
|
|
119
134
|
## Golden ratio (φ ≈ 1.618)
|
|
120
135
|
|
|
@@ -441,14 +441,8 @@ function DatePicker(props) {
|
|
|
441
441
|
event.currentTarget.closest("div")?.querySelector("input:not([type=hidden])")?.focus();
|
|
442
442
|
clear();
|
|
443
443
|
},
|
|
444
|
-
className:
|
|
445
|
-
children: clearControl.clearIcon ?? /* @__PURE__ */ jsx(
|
|
446
|
-
X,
|
|
447
|
-
{
|
|
448
|
-
className: range ? "ui-month-picker-icon" : "ui-control-inline-affix-icon",
|
|
449
|
-
"aria-hidden": "true"
|
|
450
|
-
}
|
|
451
|
-
)
|
|
444
|
+
className: "ui-control-inline-affix-action",
|
|
445
|
+
children: clearControl.clearIcon ?? /* @__PURE__ */ jsx(X, { className: "ui-control-inline-affix-icon", "aria-hidden": "true" })
|
|
452
446
|
}
|
|
453
447
|
) : /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
454
448
|
"button",
|
|
@@ -457,14 +451,8 @@ function DatePicker(props) {
|
|
|
457
451
|
disabled: allDisabled,
|
|
458
452
|
tabIndex: -1,
|
|
459
453
|
"aria-label": triggerLabel ?? (isPeriod ? t("dataEntry.monthPicker.openGrid") : range ? t("dataEntry.dateRangePicker.openCalendar") : t("dataEntry.datePicker.openCalendar")) ?? "Open calendar",
|
|
460
|
-
className:
|
|
461
|
-
children: /* @__PURE__ */ jsx(
|
|
462
|
-
CalendarIcon,
|
|
463
|
-
{
|
|
464
|
-
className: range ? "ui-month-picker-icon" : "ui-control-inline-affix-icon",
|
|
465
|
-
"aria-hidden": "true"
|
|
466
|
-
}
|
|
467
|
-
)
|
|
454
|
+
className: "ui-control-inline-affix-action",
|
|
455
|
+
children: /* @__PURE__ */ jsx(CalendarIcon, { className: "ui-control-inline-affix-icon", "aria-hidden": "true" })
|
|
468
456
|
}
|
|
469
457
|
) });
|
|
470
458
|
const panel = /* @__PURE__ */ jsxs(
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { DragAxisProp, DraggablePanelPositionProp } from "../../props/components/layout.prop.js";
|
|
3
|
-
export type { DraggablePanelProp, DraggablePanelProp as DraggablePanelProps, DraggablePanelPlacementProp, DraggablePanelPositionProp, DragAxisProp, DragBoundsProp, } from "../../props/components/layout.prop.js";
|
|
3
|
+
export type { DraggablePanelProp, DraggablePanelProp as DraggablePanelProps, DraggablePanelPlacementProp, DraggablePanelPositionProp, DraggablePanelLabels, DragAxisProp, DragBoundsProp, } from "../../props/components/layout.prop.js";
|
|
4
4
|
export declare const DraggablePanel: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLElement>, "children" | "onDrag" | "title"> & {
|
|
5
5
|
title: import("../../props/index.js").TitleProp;
|
|
6
6
|
children?: import("../../props/index.js").ChildrenProp;
|
|
7
7
|
extra?: import("../../props/index.js").ExtraProp;
|
|
8
8
|
placement?: import("./draggable-panel.js").DraggablePanelPlacementProp;
|
|
9
|
-
width?: Extract<import("../../props/index.js").SizeProp, "sm" | "md" | "lg"
|
|
9
|
+
width?: Extract<import("../../props/index.js").SizeProp, "sm" | "md" | "lg"> | "xl";
|
|
10
10
|
axis?: DragAxisProp;
|
|
11
11
|
bounds?: import("./draggable-panel.js").DragBoundsProp;
|
|
12
12
|
position?: DraggablePanelPositionProp;
|
|
13
13
|
defaultPosition?: DraggablePanelPositionProp;
|
|
14
14
|
onPositionChange?: (position: DraggablePanelPositionProp) => void;
|
|
15
15
|
onClose?: () => void;
|
|
16
|
+
labels?: import("./draggable-panel.js").DraggablePanelLabels;
|
|
16
17
|
disabled?: import("../../props/index.js").DisabledProp;
|
|
17
18
|
className?: import("../../props/index.js").ClassNameProp;
|
|
18
19
|
} & React.RefAttributes<HTMLElement>>;
|
|
@@ -51,6 +51,7 @@ const DraggablePanel = React.forwardRef(
|
|
|
51
51
|
defaultPosition,
|
|
52
52
|
onPositionChange,
|
|
53
53
|
onClose,
|
|
54
|
+
labels,
|
|
54
55
|
disabled = false,
|
|
55
56
|
className,
|
|
56
57
|
...props
|
|
@@ -73,6 +74,14 @@ const DraggablePanel = React.forwardRef(
|
|
|
73
74
|
[bounds, onPositionChange, position]
|
|
74
75
|
);
|
|
75
76
|
React.useEffect(() => () => stopDrag.current?.(), []);
|
|
77
|
+
React.useEffect(() => {
|
|
78
|
+
if (bounds !== "viewport" || typeof window === "undefined") return;
|
|
79
|
+
const onResize = () => {
|
|
80
|
+
commit(currentRef.current);
|
|
81
|
+
};
|
|
82
|
+
window.addEventListener("resize", onResize);
|
|
83
|
+
return () => window.removeEventListener("resize", onResize);
|
|
84
|
+
}, [bounds, commit]);
|
|
76
85
|
const setPanel = React.useCallback(
|
|
77
86
|
(node) => {
|
|
78
87
|
panelRef.current = node;
|
|
@@ -156,7 +165,7 @@ const DraggablePanel = React.forwardRef(
|
|
|
156
165
|
type: "button",
|
|
157
166
|
"data-slot": "draggable-panel-handle",
|
|
158
167
|
className: "ui-draggable-panel-handle ui-focus-ring",
|
|
159
|
-
"aria-label": t("layout.draggablePanel.moveLabel"),
|
|
168
|
+
"aria-label": labels?.move ?? t("layout.draggablePanel.moveLabel"),
|
|
160
169
|
"aria-disabled": movable ? void 0 : "true",
|
|
161
170
|
onPointerDown: handlePointerDown,
|
|
162
171
|
onKeyDown: handleKeyDown,
|
|
@@ -171,7 +180,7 @@ const DraggablePanel = React.forwardRef(
|
|
|
171
180
|
type: "button",
|
|
172
181
|
"data-slot": "draggable-panel-close",
|
|
173
182
|
className: "ui-draggable-panel-close ui-focus-ring",
|
|
174
|
-
"aria-label": t("feedback.alert.dismiss"),
|
|
183
|
+
"aria-label": labels?.close ?? t("feedback.alert.dismiss"),
|
|
175
184
|
onClick: onClose,
|
|
176
185
|
children: /* @__PURE__ */ jsx(X, { className: "ui-draggable-panel-close-icon", "aria-hidden": "true" })
|
|
177
186
|
}
|
|
@@ -50,7 +50,7 @@ export type { MasterDetailProps } from "./master-detail.js";
|
|
|
50
50
|
export { SplitPane } from "./split-pane.js";
|
|
51
51
|
export type { SplitPaneProps } from "./split-pane.js";
|
|
52
52
|
export { DraggablePanel } from "./draggable-panel.js";
|
|
53
|
-
export type { DraggablePanelProp, DraggablePanelProps, DraggablePanelPlacementProp, DraggablePanelPositionProp, DragAxisProp, DragBoundsProp, } from "./draggable-panel.js";
|
|
53
|
+
export type { DraggablePanelProp, DraggablePanelProps, DraggablePanelPlacementProp, DraggablePanelPositionProp, DraggablePanelLabels, DragAxisProp, DragBoundsProp, } from "./draggable-panel.js";
|
|
54
54
|
export { Separator } from "./separator.js";
|
|
55
55
|
export type { SeparatorProp, SeparatorProps } from "./separator.js";
|
|
56
56
|
export { AspectRatio } from "./aspect-ratio.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { AppLauncherApp, AppLauncherGroup, AppLauncherLabels, AppLauncherProp, AppShellProp, AuthFooterProp, AuthIdentityProp, OrgSwitcherLabels, OrgSwitcherOrganization, OrgSwitcherProp, DragAxisProp, DragBoundsProp, DraggablePanelPlacementProp, DraggablePanelPositionProp, DraggablePanelProp, PageContainerProp, PageInsetProp, SidebarItemProp, SidebarProductProp, SidebarProp, SidebarSectionProp, TopbarProp, } from "./layout.prop.js";
|
|
1
|
+
export type { AppLauncherApp, AppLauncherGroup, AppLauncherLabels, AppLauncherProp, AppShellProp, AuthFooterProp, AuthIdentityProp, OrgSwitcherLabels, OrgSwitcherOrganization, OrgSwitcherProp, DragAxisProp, DragBoundsProp, DraggablePanelLabels, DraggablePanelPlacementProp, DraggablePanelPositionProp, DraggablePanelProp, PageContainerProp, PageInsetProp, SidebarItemProp, SidebarProductProp, SidebarProp, SidebarSectionProp, TopbarProp, } from "./layout.prop.js";
|
|
2
2
|
export type { ButtonProp } from "./general.prop.js";
|
|
3
3
|
export type { InputProp, TextareaProp, FormFieldProp, SearchInputProp, CheckboxProp, CheckboxGroupProp, ChoiceOptionProp, RadioProp, RadioGroupProp, SwitchProp, SliderProp, CalendarProp, DatePickerProp, TimePickerProp, ColorPickerProp, UploadProp, UploadFileItemProp, UploadVariantProp, TreeOptionProp, TreeFieldNamesProp, CascaderProp, TreeSelectProp, ShowCheckedStrategyProp, TransferProp, TransferItemProp, } from "./data-entry.prop.js";
|
|
4
4
|
export type { AvatarProp, EmptyStateProp, DescriptionsProp, DescriptionsItemProp, BadgeProp, DataTableProp, QrCodeProp, } from "./data-display.prop.js";
|
|
@@ -1442,6 +1442,11 @@ export type DraggablePanelPositionProp = {
|
|
|
1442
1442
|
x: number;
|
|
1443
1443
|
y: number;
|
|
1444
1444
|
};
|
|
1445
|
+
/** Optional overrides for title-bar control labels when `AppProvider` is not in scope (gh#606). */
|
|
1446
|
+
export type DraggablePanelLabels = {
|
|
1447
|
+
close?: string;
|
|
1448
|
+
move?: string;
|
|
1449
|
+
};
|
|
1445
1450
|
/**
|
|
1446
1451
|
* @see DraggablePanel — a floating surface the person using it can MOVE, so a docked assistant
|
|
1447
1452
|
* stops covering the thing they are asking about.
|
|
@@ -1458,8 +1463,8 @@ export type DraggablePanelProp = Omit<React.HTMLAttributes<HTMLElement>, "title"
|
|
|
1458
1463
|
extra?: ExtraProp;
|
|
1459
1464
|
/** Resting corner before any movement. Default `bottom-end`. */
|
|
1460
1465
|
placement?: DraggablePanelPlacementProp;
|
|
1461
|
-
/** Panel width, from the token scale. Default `md`. */
|
|
1462
|
-
width?: Extract<SizeProp, "sm" | "md" | "lg"
|
|
1466
|
+
/** Panel width, from the token scale (`sm`–`xl`). Default `md`. */
|
|
1467
|
+
width?: Extract<SizeProp, "sm" | "md" | "lg"> | "xl";
|
|
1463
1468
|
/** react-draggable `axis`. Default `both`. */
|
|
1464
1469
|
axis?: DragAxisProp;
|
|
1465
1470
|
/** react-draggable `bounds`. Default `viewport`. */
|
|
@@ -1476,6 +1481,12 @@ export type DraggablePanelProp = Omit<React.HTMLAttributes<HTMLElement>, "title"
|
|
|
1476
1481
|
onPositionChange?: (position: DraggablePanelPositionProp) => void;
|
|
1477
1482
|
/** Presence renders the close control in the title bar (antd Modal's `onCancel`). */
|
|
1478
1483
|
onClose?: () => void;
|
|
1484
|
+
/**
|
|
1485
|
+
* Localized strings for the close and move controls. Each key wins over `t()` when set; omit to
|
|
1486
|
+
* keep the framework default. For script-injected embeds that cannot mount `AppProvider` without
|
|
1487
|
+
* restyling the host page — a scoped provider subtree is the long-term fix (gh#606).
|
|
1488
|
+
*/
|
|
1489
|
+
labels?: DraggablePanelLabels;
|
|
1479
1490
|
/** react-draggable `disabled` — the panel stays, the handle stops moving it. */
|
|
1480
1491
|
disabled?: DisabledProp;
|
|
1481
1492
|
className?: ClassNameProp;
|
package/dist/props/registry.d.ts
CHANGED
|
@@ -2536,6 +2536,10 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
2536
2536
|
readonly field: "onClose";
|
|
2537
2537
|
readonly local: true;
|
|
2538
2538
|
readonly reason: "Presence renders the title-bar close control — antd Modal's onCancel.";
|
|
2539
|
+
}, {
|
|
2540
|
+
readonly field: "labels";
|
|
2541
|
+
readonly local: true;
|
|
2542
|
+
readonly reason: "Optional close/move strings for embeds that cannot mount AppProvider without restyling the host page (gh#606).";
|
|
2539
2543
|
}];
|
|
2540
2544
|
};
|
|
2541
2545
|
readonly ThumbnailSizeProp: {
|
package/dist/props/registry.js
CHANGED
|
@@ -2953,6 +2953,11 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
2953
2953
|
field: "onClose",
|
|
2954
2954
|
local: true,
|
|
2955
2955
|
reason: "Presence renders the title-bar close control \u2014 antd Modal's onCancel."
|
|
2956
|
+
},
|
|
2957
|
+
{
|
|
2958
|
+
field: "labels",
|
|
2959
|
+
local: true,
|
|
2960
|
+
reason: "Optional close/move strings for embeds that cannot mount AppProvider without restyling the host page (gh#606)."
|
|
2956
2961
|
}
|
|
2957
2962
|
]
|
|
2958
2963
|
},
|
package/dist/styles/control.css
CHANGED
|
@@ -1355,6 +1355,27 @@
|
|
|
1355
1355
|
block-size: 0.75rem;
|
|
1356
1356
|
}
|
|
1357
1357
|
|
|
1358
|
+
.ui-number-input-step {
|
|
1359
|
+
position: relative;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
.ui-number-input-step::after {
|
|
1363
|
+
content: "";
|
|
1364
|
+
position: absolute;
|
|
1365
|
+
inset-inline-start: 50%;
|
|
1366
|
+
translate: -50% 0;
|
|
1367
|
+
inline-size: var(--touch-target-min);
|
|
1368
|
+
block-size: var(--touch-target-min);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
.ui-number-input-step-up::after {
|
|
1372
|
+
inset-block-end: 0;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
.ui-number-input-step-down::after {
|
|
1376
|
+
inset-block-start: 0;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1358
1379
|
@media (pointer: coarse) {
|
|
1359
1380
|
.ui-number-input:not([data-size="xs"]):not([data-size="sm"]) {
|
|
1360
1381
|
--control-height: var(--number-input-touch-height);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
|
|
2
|
+
@font-face {
|
|
3
|
+
font-family: "Noto Sans JP Fallback";
|
|
4
|
+
font-weight: 400;
|
|
5
|
+
src: local("Arial"), local("ArialMT"), local("Liberation Sans"), local("LiberationSans");
|
|
6
|
+
size-adjust: 103.02%;
|
|
7
|
+
ascent-override: 112.6%;
|
|
8
|
+
descent-override: 27.96%;
|
|
9
|
+
line-gap-override: 0%;
|
|
10
|
+
unicode-range:
|
|
11
|
+
U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
|
|
12
|
+
U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
|
|
13
|
+
U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
|
|
14
|
+
}
|
|
15
|
+
@font-face {
|
|
16
|
+
font-family: "Noto Sans JP Fallback";
|
|
17
|
+
font-weight: 500;
|
|
18
|
+
src: local("Arial"), local("ArialMT"), local("Liberation Sans"), local("LiberationSans");
|
|
19
|
+
size-adjust: 105.5%;
|
|
20
|
+
ascent-override: 109.95%;
|
|
21
|
+
descent-override: 27.3%;
|
|
22
|
+
line-gap-override: 0%;
|
|
23
|
+
unicode-range:
|
|
24
|
+
U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
|
|
25
|
+
U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
|
|
26
|
+
U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
|
|
27
|
+
}
|
|
28
|
+
@font-face {
|
|
29
|
+
font-family: "Noto Sans JP Fallback";
|
|
30
|
+
font-weight: 700;
|
|
31
|
+
src:
|
|
32
|
+
local("Arial Bold"), local("Arial-BoldMT"), local("Liberation Sans Bold"),
|
|
33
|
+
local("LiberationSans-Bold");
|
|
34
|
+
size-adjust: 101.5%;
|
|
35
|
+
ascent-override: 114.29%;
|
|
36
|
+
descent-override: 28.37%;
|
|
37
|
+
line-gap-override: 0%;
|
|
38
|
+
unicode-range:
|
|
39
|
+
U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
|
|
40
|
+
U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
|
|
41
|
+
U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
|
|
42
|
+
}
|
|
43
|
+
@font-face {
|
|
44
|
+
font-family: "Noto Sans JP Fallback";
|
|
45
|
+
font-weight: 400;
|
|
46
|
+
src:
|
|
47
|
+
local("Hiragino Sans W3"), local("HiraginoSans-W3"), local("Hiragino Kaku Gothic ProN W3"),
|
|
48
|
+
local("HiraKakuProN-W3"), local("Yu Gothic Medium"), local("YuGothic-Medium"), local("Meiryo"),
|
|
49
|
+
local("Noto Sans CJK JP"), local("NotoSansCJKjp-Regular"), local("IPAGothic");
|
|
50
|
+
ascent-override: 116%;
|
|
51
|
+
descent-override: 28.8%;
|
|
52
|
+
line-gap-override: 0%;
|
|
53
|
+
unicode-range:
|
|
54
|
+
U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
|
|
55
|
+
}
|
|
56
|
+
@font-face {
|
|
57
|
+
font-family: "Noto Sans JP Fallback";
|
|
58
|
+
font-weight: 500;
|
|
59
|
+
src:
|
|
60
|
+
local("Hiragino Sans W5"), local("HiraginoSans-W5"), local("Hiragino Kaku Gothic ProN W3"),
|
|
61
|
+
local("HiraKakuProN-W3"), local("Yu Gothic Medium"), local("YuGothic-Medium"), local("Meiryo"),
|
|
62
|
+
local("Noto Sans CJK JP Medium"), local("NotoSansCJKjp-Medium"), local("Noto Sans CJK JP"),
|
|
63
|
+
local("NotoSansCJKjp-Regular"), local("IPAGothic");
|
|
64
|
+
ascent-override: 116%;
|
|
65
|
+
descent-override: 28.8%;
|
|
66
|
+
line-gap-override: 0%;
|
|
67
|
+
unicode-range:
|
|
68
|
+
U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
|
|
69
|
+
}
|
|
70
|
+
@font-face {
|
|
71
|
+
font-family: "Noto Sans JP Fallback";
|
|
72
|
+
font-weight: 700;
|
|
73
|
+
src:
|
|
74
|
+
local("Hiragino Sans W6"), local("HiraginoSans-W6"), local("Hiragino Kaku Gothic ProN W6"),
|
|
75
|
+
local("HiraKakuProN-W6"), local("Yu Gothic Bold"), local("YuGothic-Bold"), local("Meiryo Bold"),
|
|
76
|
+
local("Meiryo-Bold"), local("Noto Sans CJK JP Bold"), local("NotoSansCJKjp-Bold"),
|
|
77
|
+
local("IPAGothic");
|
|
78
|
+
ascent-override: 116%;
|
|
79
|
+
descent-override: 28.8%;
|
|
80
|
+
line-gap-override: 0%;
|
|
81
|
+
unicode-range:
|
|
82
|
+
U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
|
|
83
|
+
}
|
package/dist/styles/fonts.css
CHANGED
|
@@ -6,88 +6,7 @@
|
|
|
6
6
|
@import "@fontsource/noto-sans-jp/500.css";
|
|
7
7
|
@import "@fontsource/noto-sans-jp/700.css";
|
|
8
8
|
|
|
9
|
-
@font-
|
|
10
|
-
font-family: "Noto Sans JP Fallback";
|
|
11
|
-
font-weight: 400;
|
|
12
|
-
src: local("Arial"), local("ArialMT"), local("Liberation Sans"), local("LiberationSans");
|
|
13
|
-
size-adjust: 103.02%;
|
|
14
|
-
ascent-override: 112.6%;
|
|
15
|
-
descent-override: 27.96%;
|
|
16
|
-
line-gap-override: 0%;
|
|
17
|
-
unicode-range:
|
|
18
|
-
U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
|
|
19
|
-
U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
|
|
20
|
-
U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
|
|
21
|
-
}
|
|
22
|
-
@font-face {
|
|
23
|
-
font-family: "Noto Sans JP Fallback";
|
|
24
|
-
font-weight: 500;
|
|
25
|
-
src: local("Arial"), local("ArialMT"), local("Liberation Sans"), local("LiberationSans");
|
|
26
|
-
size-adjust: 105.5%;
|
|
27
|
-
ascent-override: 109.95%;
|
|
28
|
-
descent-override: 27.3%;
|
|
29
|
-
line-gap-override: 0%;
|
|
30
|
-
unicode-range:
|
|
31
|
-
U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
|
|
32
|
-
U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
|
|
33
|
-
U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
|
|
34
|
-
}
|
|
35
|
-
@font-face {
|
|
36
|
-
font-family: "Noto Sans JP Fallback";
|
|
37
|
-
font-weight: 700;
|
|
38
|
-
src:
|
|
39
|
-
local("Arial Bold"), local("Arial-BoldMT"), local("Liberation Sans Bold"),
|
|
40
|
-
local("LiberationSans-Bold");
|
|
41
|
-
size-adjust: 101.5%;
|
|
42
|
-
ascent-override: 114.29%;
|
|
43
|
-
descent-override: 28.37%;
|
|
44
|
-
line-gap-override: 0%;
|
|
45
|
-
unicode-range:
|
|
46
|
-
U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
|
|
47
|
-
U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
|
|
48
|
-
U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
|
|
49
|
-
}
|
|
50
|
-
@font-face {
|
|
51
|
-
font-family: "Noto Sans JP Fallback";
|
|
52
|
-
font-weight: 400;
|
|
53
|
-
src:
|
|
54
|
-
local("Hiragino Sans W3"), local("HiraginoSans-W3"), local("Hiragino Kaku Gothic ProN W3"),
|
|
55
|
-
local("HiraKakuProN-W3"), local("Yu Gothic Medium"), local("YuGothic-Medium"), local("Meiryo"),
|
|
56
|
-
local("Noto Sans CJK JP"), local("NotoSansCJKjp-Regular"), local("IPAGothic");
|
|
57
|
-
ascent-override: 116%;
|
|
58
|
-
descent-override: 28.8%;
|
|
59
|
-
line-gap-override: 0%;
|
|
60
|
-
unicode-range:
|
|
61
|
-
U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
|
|
62
|
-
}
|
|
63
|
-
@font-face {
|
|
64
|
-
font-family: "Noto Sans JP Fallback";
|
|
65
|
-
font-weight: 500;
|
|
66
|
-
src:
|
|
67
|
-
local("Hiragino Sans W5"), local("HiraginoSans-W5"), local("Hiragino Kaku Gothic ProN W3"),
|
|
68
|
-
local("HiraKakuProN-W3"), local("Yu Gothic Medium"), local("YuGothic-Medium"), local("Meiryo"),
|
|
69
|
-
local("Noto Sans CJK JP Medium"), local("NotoSansCJKjp-Medium"), local("Noto Sans CJK JP"),
|
|
70
|
-
local("NotoSansCJKjp-Regular"), local("IPAGothic");
|
|
71
|
-
ascent-override: 116%;
|
|
72
|
-
descent-override: 28.8%;
|
|
73
|
-
line-gap-override: 0%;
|
|
74
|
-
unicode-range:
|
|
75
|
-
U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
|
|
76
|
-
}
|
|
77
|
-
@font-face {
|
|
78
|
-
font-family: "Noto Sans JP Fallback";
|
|
79
|
-
font-weight: 700;
|
|
80
|
-
src:
|
|
81
|
-
local("Hiragino Sans W6"), local("HiraginoSans-W6"), local("Hiragino Kaku Gothic ProN W6"),
|
|
82
|
-
local("HiraKakuProN-W6"), local("Yu Gothic Bold"), local("YuGothic-Bold"), local("Meiryo Bold"),
|
|
83
|
-
local("Meiryo-Bold"), local("Noto Sans CJK JP Bold"), local("NotoSansCJKjp-Bold"),
|
|
84
|
-
local("IPAGothic");
|
|
85
|
-
ascent-override: 116%;
|
|
86
|
-
descent-override: 28.8%;
|
|
87
|
-
line-gap-override: 0%;
|
|
88
|
-
unicode-range:
|
|
89
|
-
U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
|
|
90
|
-
}
|
|
9
|
+
@import "./font-fallbacks.css";
|
|
91
10
|
|
|
92
11
|
:root {
|
|
93
12
|
--font-sans-base:
|
package/dist/styles/layout.css
CHANGED
|
@@ -1799,6 +1799,7 @@
|
|
|
1799
1799
|
flex-direction: column;
|
|
1800
1800
|
inline-size: var(--draggable-panel-width);
|
|
1801
1801
|
max-inline-size: calc(100% - var(--draggable-panel-inset) - var(--draggable-panel-inset));
|
|
1802
|
+
block-size: var(--draggable-panel-block-size);
|
|
1802
1803
|
max-block-size: var(--draggable-panel-max-block-size);
|
|
1803
1804
|
border: var(--draggable-panel-border-width) solid hsl(var(--border));
|
|
1804
1805
|
border-radius: var(--draggable-panel-radius);
|
|
@@ -1818,6 +1819,10 @@
|
|
|
1818
1819
|
inline-size: var(--draggable-panel-width-lg);
|
|
1819
1820
|
}
|
|
1820
1821
|
|
|
1822
|
+
.ui-draggable-panel[data-width="xl"] {
|
|
1823
|
+
inline-size: var(--draggable-panel-width-xl);
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1821
1826
|
.ui-draggable-panel[data-placement^="top"] {
|
|
1822
1827
|
inset-block-start: var(--draggable-panel-inset);
|
|
1823
1828
|
}
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
--draggable-panel-width-sm: 18rem;
|
|
13
13
|
--draggable-panel-width: 22rem;
|
|
14
14
|
--draggable-panel-width-lg: 28rem;
|
|
15
|
+
|
|
16
|
+
--draggable-panel-width-xl: var(--centered-shell-width-md);
|
|
17
|
+
|
|
18
|
+
--draggable-panel-block-size: auto;
|
|
15
19
|
--draggable-panel-max-block-size: 70vh;
|
|
16
20
|
|
|
17
21
|
--draggable-panel-radius: var(--radius-lg);
|
package/docs/CONSUMER-RULES.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Read this once; the audit enforces it. Everything else in `docs/` is for contributors.
|
|
4
4
|
|
|
5
|
-
1. Load styles with `@import "@godxjp/ui/styles"` (fonts bundled) or `@import "@godxjp/ui/styles/core"` (
|
|
5
|
+
1. Load styles with `@import "@godxjp/ui/styles"` (fonts bundled), `@import "@godxjp/ui/styles/core"` (no `@font-face` at all) or `@import "@godxjp/ui/styles/core-with-fallbacks"` (`core` + six `local()`-only metric-matched faces, still zero network bytes — for when you supply Noto Sans JP yourself). Never cherry-pick `*-layout.css`.
|
|
6
6
|
2. Every page is `<PageContainer title subtitle extra footer>`; its sections are spaced by the page. Group items inside a section with `<Flex direction="col" gap>` or `<ResponsiveGrid>`.
|
|
7
7
|
3. No Tailwind layout on your own elements: no `flex`, `grid`, `gap-*`, `p-*`, `m-*`, `space-*`. Rows are `<Flex>` (default row), stacks are `<Flex direction="col">`, grids are `<ResponsiveGrid>`.
|
|
8
8
|
4. No hand-rolled surfaces: no `rounded-* border bg-*` divs. A box is `Card`, a pill is `Badge`, a person is `Avatar`, a row is `ListRow`, a label/value pair is `Descriptions`, an empty area is `EmptyState`, a read-only sample of a colour the USER chose is `Swatch`.
|
package/docs/CUSTOMER-THEMING.md
CHANGED
|
@@ -16,14 +16,28 @@ That single import ships everything: colors, the bundled fonts (**Noto Sans JP**
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
## CSS entries — `styles`
|
|
19
|
+
## CSS entries — `styles`, `styles/core`, `styles/core-with-fallbacks`, nothing smaller
|
|
20
20
|
|
|
21
|
-
`@godxjp/ui/styles` bundles every component's CSS plus the fonts. When you manage fonts yourself (next/font, a system stack, a browser extension that must not ship font files), load the same layers without the faces:
|
|
21
|
+
`@godxjp/ui/styles` bundles every component's CSS plus the fonts: **729 woff2 subsets, ~11.7 MB on disk** at the current @fontsource versions (issue #535 measured 737 files / 13 MB in a real consumer build). When you manage fonts yourself (next/font, a system stack, a browser extension that must not ship font files), load the same layers without the faces:
|
|
22
22
|
|
|
23
23
|
```css
|
|
24
24
|
@import "@godxjp/ui/styles/core"; /* every component layer, no @font-face */
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
`core` carries **zero** `@font-face` — `grep -c '@font-face' node_modules/@godxjp/ui/dist/styles/core.css` → `0` — and that number is the point of it. If you supply Noto Sans JP yourself and also want the cold-visit swap to stop reflowing the page (issue #475), take the third entry: `core` plus the six metric-matched fallback faces, every one `local()`-only, so the extra cost over `core` is **zero network bytes**.
|
|
28
|
+
|
|
29
|
+
```css
|
|
30
|
+
@import "@godxjp/ui/styles/core-with-fallbacks"; /* core + 6 local()-only faces */
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
It declares the faces and nothing else — name the family yourself, directly after your own face:
|
|
34
|
+
|
|
35
|
+
```css
|
|
36
|
+
:root {
|
|
37
|
+
--font-sans-base: "Noto Sans JP", "Noto Sans JP Fallback", system-ui, sans-serif;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
27
41
|
The per-layer files (`control`, `card-layout`, `navigation-layout`, …) are the package's internal structure, **not a public menu**. Layers share rules — a Select's rows and a menu's surface, a form's rhythm, a card's header type — so a page that loads a subset renders naked menus and unsized rows with no error. The runtime `visual-audit` reports it as `css-layers-missing`.
|
|
28
42
|
|
|
29
43
|
---
|
package/docs/DESIGN-AUTHORITY.md
CHANGED
|
@@ -247,6 +247,10 @@ in page CSS.
|
|
|
247
247
|
(gh#602, measured 1.00:1). This library adds `count` / `overflowCount` / `showZero` /
|
|
248
248
|
`countLabel` on `SegmentedOption` with a DS-owned pill (`--segmented-count-*` tokens), same
|
|
249
249
|
counter vocabulary as `Button` / `Toggle`.
|
|
250
|
+
- **`DraggablePanel.labels`.** antd's Draggable Modal demo has no i18n hook for the drag handle.
|
|
251
|
+
Embedded consumers cannot mount `AppProvider` without writing theme attributes on the host
|
|
252
|
+
`document.documentElement` (gh#606). `labels?: { close?: string; move?: string }` overrides `t()`
|
|
253
|
+
per control; a scoped `AppProvider` subtree remains the long-term fix.
|
|
250
254
|
- **`Badge.onRemove` instead of antd `Tag`'s `closable` + `onClose`.** Semantics match antd's
|
|
251
255
|
closable tag (a chip that draws its own × and fires one callback). The boolean is folded into
|
|
252
256
|
prop presence (`onRemove` omitted ⇒ no ×), and the callback is named `onRemove` rather than
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godxjp/ui",
|
|
3
|
-
"version": "23.4.
|
|
4
|
-
"godxUiMcp": "23.4.
|
|
3
|
+
"version": "23.4.3",
|
|
4
|
+
"godxUiMcp": "23.4.3",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -46,6 +46,8 @@
|
|
|
46
46
|
"import": "./dist/index.js"
|
|
47
47
|
},
|
|
48
48
|
"./styles": "./dist/styles/index.css",
|
|
49
|
+
"./styles/core": "./dist/styles/core.css",
|
|
50
|
+
"./styles/core-with-fallbacks": "./dist/styles/core-with-fallbacks.css",
|
|
49
51
|
"./styles/*": "./dist/styles/*.css",
|
|
50
52
|
"./theme/*": "./dist/theme/*",
|
|
51
53
|
"./tokens": "./dist/tokens/base.css",
|
package/scripts/_agent-setup.mjs
CHANGED
|
@@ -125,6 +125,18 @@ export function stampedDigest(text) {
|
|
|
125
125
|
return text?.match(DIGEST_RE)?.[1] ?? null;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Refresh only the human-readable release marker when the managed body is already current.
|
|
130
|
+
*
|
|
131
|
+
* The writer keys off digest; `ui-audit` keys off version (godx-jp/id#513). When rule text is
|
|
132
|
+
* unchanged across releases both must still agree after sync — without rewriting the body or
|
|
133
|
+
* breaking idempotency when digest and version already match.
|
|
134
|
+
*/
|
|
135
|
+
function resyncVersionStamp(text) {
|
|
136
|
+
if (stampedVersion(text) === KIT_VERSION) return null;
|
|
137
|
+
return text.replace(STAMP_RE, `<!-- godxjp-ui:version ${KIT_VERSION} -->`);
|
|
138
|
+
}
|
|
139
|
+
|
|
128
140
|
/**
|
|
129
141
|
* Replace the MANAGED region of a file and leave everything else alone.
|
|
130
142
|
*
|
|
@@ -533,7 +545,14 @@ export function refreshGuineaPigSkill(root) {
|
|
|
533
545
|
* to anyone reading it. Only the digest half is compared.
|
|
534
546
|
*/
|
|
535
547
|
const stamp = `${KIT_VERSION}:${digestOf(base)}`;
|
|
536
|
-
|
|
548
|
+
const optinLine = readFileSync(optin, "utf8").trim();
|
|
549
|
+
const optinDigest = optinLine.split(":").pop();
|
|
550
|
+
if (optinDigest === digestOf(base)) {
|
|
551
|
+
if (optinLine.split(":")[0] === KIT_VERSION) return false;
|
|
552
|
+
// Base unchanged — only the release half of `<version>:<digest>` drifted (same split as above).
|
|
553
|
+
writeFileAtomic(optin, `${stamp}\n`);
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
537
556
|
|
|
538
557
|
const current = readFileSync(target, "utf8");
|
|
539
558
|
const marker = "\n---\n\n# 8. ";
|
|
@@ -581,8 +600,14 @@ export function ensureConsumerRules(root) {
|
|
|
581
600
|
const managed = `${front}${body}`;
|
|
582
601
|
const next = `${OWNED_STAMP(managed)}\n${managed}`;
|
|
583
602
|
|
|
584
|
-
if (existsSync(target)
|
|
585
|
-
|
|
603
|
+
if (existsSync(target)) {
|
|
604
|
+
const current = readFileSync(target, "utf8");
|
|
605
|
+
if (stampedDigest(current) === digestOf(managed)) {
|
|
606
|
+
const resynced = resyncVersionStamp(current);
|
|
607
|
+
if (resynced == null) return false;
|
|
608
|
+
writeFileAtomic(target, resynced);
|
|
609
|
+
return uiDir;
|
|
610
|
+
}
|
|
586
611
|
}
|
|
587
612
|
mkdirSync(dir, { recursive: true });
|
|
588
613
|
writeFileAtomic(target, next);
|
package/scripts/cli.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @godxjp/ui CLI — `npx @godxjp/ui <command>`.
|
|
4
4
|
* init-agent scaffold the agent forcing-kit (MCP + auto-audit hook + mandate)
|
|
5
|
+
* sync-rules refresh package-owned agent rules (same path as postinstall)
|
|
5
6
|
* audit static UI audit (regex over source)
|
|
6
7
|
* visual-audit runtime audit (Playwright + axe-core) against a running app
|
|
7
8
|
*/
|
|
@@ -12,6 +13,7 @@ import { fileURLToPath } from "node:url";
|
|
|
12
13
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
const MAP = {
|
|
14
15
|
"init-agent": "init-agent-kit.mjs",
|
|
16
|
+
"sync-rules": "postinstall.mjs",
|
|
15
17
|
audit: "ui-audit.mjs",
|
|
16
18
|
"visual-audit": "visual-audit.mjs",
|
|
17
19
|
};
|
|
@@ -19,8 +21,10 @@ const MAP = {
|
|
|
19
21
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
20
22
|
const script = MAP[cmd];
|
|
21
23
|
if (!script) {
|
|
22
|
-
console.error("usage: godxjp-ui <init-agent | audit | visual-audit> [args]");
|
|
24
|
+
console.error("usage: godxjp-ui <init-agent | sync-rules | audit | visual-audit> [args]");
|
|
23
25
|
process.exit(1);
|
|
24
26
|
}
|
|
25
|
-
const
|
|
27
|
+
const env =
|
|
28
|
+
cmd === "sync-rules" ? { ...process.env, INIT_CWD: process.env.INIT_CWD ?? process.cwd() } : process.env;
|
|
29
|
+
const r = spawnSync("node", [join(HERE, script), ...rest], { stdio: "inherit", env });
|
|
26
30
|
process.exit(r.status ?? 0);
|
package/scripts/consumer-rule.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# @godxjp/ui
|
|
2
2
|
|
|
3
|
-
> **Tệp này do gói `@godxjp/ui` sở hữu
|
|
3
|
+
> **Tệp này do gói `@godxjp/ui` sở hữu.** Nội dung được ghi lại khi luật trong gói đổi (thường
|
|
4
|
+
> qua postinstall sau `npm i`, trừ khi `.npmrc` có `ignore-scripts=true`). Khi đó chạy
|
|
5
|
+
> `npx @godxjp/ui sync-rules`. Dấu `<!-- godxjp-ui:version -->` luôn được đồng bộ với phiên bản
|
|
6
|
+
> gói đang cài — kể cả khi thân tệp không đổi giữa hai bản.
|
|
4
7
|
> Đừng sửa ở đây — luật của riêng kho thuộc về một tệp khác trong `.ai/rules/`,
|
|
5
8
|
> và index sẽ nạp cả hai. (Khác với `.claude/skills/.../SKILL.md`, nơi mục §8
|
|
6
9
|
> trở đi là của kho và được giữ lại.)
|
|
@@ -9,11 +12,12 @@
|
|
|
9
12
|
`docs/CONSUMER-RULES.md` (10 luật) và, với kho chuột bạch, ở
|
|
10
13
|
`.claude/skills/godx-ui-guinea-pig/SKILL.md`.
|
|
11
14
|
|
|
12
|
-
## Nạp style:
|
|
15
|
+
## Nạp style: BA lối vào, và hai lối sau không chở font
|
|
13
16
|
|
|
14
17
|
```css
|
|
15
|
-
@import "@godxjp/ui/styles";
|
|
18
|
+
@import "@godxjp/ui/styles"; /* mọi layer + Noto Sans JP / M PLUS 2 đóng gói sẵn */
|
|
16
19
|
@import "@godxjp/ui/styles/core"; /* CÙNG các layer ấy, KHÔNG một @font-face nào */
|
|
20
|
+
@import "@godxjp/ui/styles/core-with-fallbacks"; /* core + 6 khối local()-only, vẫn 0 byte mạng */
|
|
17
21
|
```
|
|
18
22
|
|
|
19
23
|
Chọn `core` khi kho tự lo mặt chữ, hoặc khi không muốn chở font: `@fontsource` cắt
|
|
@@ -21,9 +25,18 @@ Noto Sans JP thành hàng trăm lát `unicode-range`, và trình duyệt chỉ b
|
|
|
21
25
|
SAU khi đã dựng bố cục — một consumer đo được **737 lát / 13 MB**, gấp bảy lần toàn bộ
|
|
22
26
|
JavaScript của họ, cộng ~8 vòng tải mỗi lần chuyển màn.
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
Lối thứ ba dành cho kho **tự cấp Noto Sans JP** (next/font, self-host) mà vẫn muốn cửa
|
|
29
|
+
sổ swap không đội hình: nó chở đúng 6 `@font-face` metric-matched, `src` toàn `local()`
|
|
30
|
+
nên **không tải byte nào**. Nhớ tự xếp tên họ chữ ngay sau mặt chữ của bạn:
|
|
31
|
+
`--font-sans-base: "Noto Sans JP", "Noto Sans JP Fallback", system-ui, sans-serif;`
|
|
32
|
+
|
|
33
|
+
`core` giữ `@font-face` = **0** và đó là lời hứa đo được —
|
|
34
|
+
`grep -c '@font-face' node_modules/@godxjp/ui/dist/styles/core.css` → `0`. Vì vậy các
|
|
35
|
+
fallback nằm ở entry riêng chứ không nhét vào `core`.
|
|
36
|
+
|
|
37
|
+
Không lối nào trong ba là cherry-pick: cả ba đều được hỗ trợ và thứ tự layer vẫn nguyên
|
|
38
|
+
vẹn. Cherry-pick từng layer riêng lẻ thì vẫn cấm — đó là thứ làm vỡ hợp đồng thứ tự,
|
|
39
|
+
không phải việc chọn lối vào.
|
|
27
40
|
|
|
28
41
|
## Bố cục chuẩn của platform: BA CỘT, và ba cột là BA PHẠM VI
|
|
29
42
|
|
|
@@ -66,24 +79,69 @@ Slack thì đặt lại **một dòng**, không fork `.app-nav-rail`.
|
|
|
66
79
|
Hỏi MCP `godxjp-ui` (`search_components`, `get_component`). Đo được trong một
|
|
67
80
|
ngày: năm thứ cần đều ĐÃ CÓ và vẫn bị dựng lại bằng thứ khác —
|
|
68
81
|
|
|
69
|
-
| Cần
|
|
70
|
-
|
|
|
71
|
-
| đường kẻ chạm mép Card
|
|
72
|
-
| header có kẻ khi thân là danh sách flush
|
|
73
|
-
| một hàng LÀ liên kết (thay cho nút rời)
|
|
74
|
-
| kẻ ô từng ngày trong lịch
|
|
75
|
-
| dải giữa hai vùng, tự kẻ theo VỊ TRÍ
|
|
82
|
+
| Cần | Đã có |
|
|
83
|
+
| ----------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
84
|
+
| đường kẻ chạm mép Card | `<CardContent flush>` |
|
|
85
|
+
| header có kẻ khi thân là danh sách flush | `<CardHeader banded>` |
|
|
86
|
+
| một hàng LÀ liên kết (thay cho nút rời) | `<ListRow asChild>` |
|
|
87
|
+
| kẻ ô từng ngày trong lịch | `<Calendar bordered>` |
|
|
88
|
+
| dải giữa hai vùng, tự kẻ theo VỊ TRÍ | `<CardBar>` (`border` để ép khi xếp chồng) |
|
|
89
|
+
| chip "điều kiện đang bật" có dấu × để bỏ | `<Badge onRemove>` — KHÔNG phải `TagInput` |
|
|
90
|
+
| dải tab nằm TRONG đầu Card | `<Card tabList activeTabKey onTabChange>` |
|
|
91
|
+
| tiêu đề / đoạn văn / liên kết trong văn bản | `<Title>` · `<Paragraph>` · `<Link>` (`Text` 33 prop) |
|
|
92
|
+
| nút nổi góc màn (quay lên đầu, hành động nhanh) | `<FloatButton>` |
|
|
93
|
+
| khung xương của một FORM khi đang tải | `<SkeletonForm>` |
|
|
94
|
+
| ô màu chỉ để xem, màu do NGƯỜI DÙNG chọn | `<Swatch>` |
|
|
95
|
+
| ảnh đại diện của một tệp / bản xem trước | `<Thumbnail>` |
|
|
96
|
+
| danh sách "có gì trong gói này" | `<FeatureList>` |
|
|
97
|
+
| panel kéo giãn được (chia đôi màn) | `<DraggablePanel>` |
|
|
98
|
+
| màn hội thoại AI | `<Welcome>` · `<Conversations>` · `<Attachments>` · `<ThoughtChain>` · `<Actions>` |
|
|
76
99
|
|
|
77
100
|
Lỗi không phải "đoán sai tên prop" mà là **cho rằng nó không tồn tại nên không
|
|
78
101
|
hỏi**.
|
|
79
102
|
|
|
80
|
-
##
|
|
103
|
+
## Dialog và AlertDialog là MỘT họ — `variant` là lối chuẩn
|
|
81
104
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
105
|
+
Đừng với tay sang 12 export `AlertDialog*` nữa. Chúng **vẫn chạy y như cũ** (gỡ
|
|
106
|
+
là breaking change) nhưng là **LỐI CŨ**: đo được 26 export với **12 cặp trùng
|
|
107
|
+
tên** và **0** phần chỉ `AlertDialog` mới có. antd — thẩm quyền bề mặt prop của
|
|
108
|
+
gói này — chỉ có MỘT `Modal`, và mức nguy hiểm ở đó là một PROP.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
<DialogContent variant="destructive"> {/* thay cho <AlertDialogContent> */}
|
|
112
|
+
```
|
|
85
113
|
|
|
86
|
-
|
|
114
|
+
Một prop ấy quyết định ba thứ đi cùng nhau: `role="alertdialog"`, click ra ngoài
|
|
115
|
+
KHÔNG đóng, nút chính nhấn mạnh destructive (và ✕ mặc định tắt). Esc VẪN đóng —
|
|
116
|
+
y như `AlertDialogContent` trước nay. Đặt `variant` ở `DialogRoot` thì cả cây kế
|
|
117
|
+
thừa.
|
|
118
|
+
|
|
119
|
+
## antd là CHUẨN — thiếu gì thì port 100%, đừng tự thiết kế
|
|
120
|
+
|
|
121
|
+
`docs/DESIGN-AUTHORITY.md` của gói: **nơi antd đặt tên cho một năng lực, gói này
|
|
122
|
+
lấy nguyên tên và nguyên ngữ nghĩa của antd.** Một năng lực còn thiếu được port
|
|
123
|
+
từ antd **100% TRƯỚC** — tên, prop, ngữ nghĩa — rồi mới cải tiến. Không thiết kế
|
|
124
|
+
lại trước, không port một nửa.
|
|
125
|
+
|
|
126
|
+
Nghĩa là với consumer: thấy thiếu prop thì **mở issue kèm tên antd của nó**
|
|
127
|
+
(`sorter`, `closable`, `okType`…), đừng đề xuất một cái tên mới và đừng tự vẽ
|
|
128
|
+
lại bằng class tiện ích. Ba trục cố ý lệch khỏi antd đều đã ghi lý do trong
|
|
129
|
+
DESIGN-AUTHORITY (logical thay cho `left/right`, từ vựng giá trị của gói này,
|
|
130
|
+
`density` thay cho `size`) — lệch thêm thì phải viết ra ở đó.
|
|
131
|
+
|
|
132
|
+
## Catalog giờ chở CẢ luật bố cục — nhưng phải hỏi mới có
|
|
133
|
+
|
|
134
|
+
Mục này từng nói "catalog chở PROP, không chở LUẬT BỐ CỤC", với `CardBar` làm
|
|
135
|
+
bằng chứng: một prop (`extra`), không dòng nào nói nó tự kẻ theo vị trí. **Bằng
|
|
136
|
+
chứng ấy đã hết đúng.** `CardBar` nay có 6 prop, trong đó `border` (`"none" |
|
|
137
|
+
"block-start" | "block-end" | "both"`) ép được đường kẻ khi xếp chồng, và cả
|
|
138
|
+
`Card` lẫn `CardBar` trong catalog đều nói ra luật vị trí (đầu: kẻ dưới · cuối:
|
|
139
|
+
kẻ trên · giữa: cả hai).
|
|
140
|
+
|
|
141
|
+
Nên luật hiện hành là: **hỏi `get_component` trước** — nay nó thường trả lời cả
|
|
142
|
+
hình dạng lẫn luật. Còn khi `usage`/`description` im lặng về bố cục thì mới mở
|
|
143
|
+
`node_modules/@godxjp/ui/src/styles/*-layout.css` của component ấy ra đọc; chú
|
|
144
|
+
thích trong đó vẫn là bản đầy đủ nhất.
|
|
87
145
|
|
|
88
146
|
## Card không lồng Card
|
|
89
147
|
|
|
@@ -101,6 +101,28 @@ Thứ tự ưu tiên, chỉ tiến khi bước trước thật sự không diễ
|
|
|
101
101
|
**dùng → ghép → thêm prop vào component đã có → tạo component mới.**
|
|
102
102
|
Một prop nữa hơn một component nữa.
|
|
103
103
|
|
|
104
|
+
**Và TÊN không phải của bạn — nó là của antd.** `docs/DESIGN-AUTHORITY.md` (mục
|
|
105
|
+
"The PROP SURFACE of a component is antd's too"): **nơi antd đặt tên cho một năng
|
|
106
|
+
lực, kho này lấy nguyên tên và nguyên ngữ nghĩa của antd. Một năng lực còn thiếu
|
|
107
|
+
được port từ antd 100% TRƯỚC — tên, prop, ngữ nghĩa — rồi mới cải tiến. Không
|
|
108
|
+
thiết kế lại trước, và không port một nửa.**
|
|
109
|
+
|
|
110
|
+
Đây là luật mới nhất và là luật hay bị bỏ qua nhất, vì nó nghe như lời khuyên.
|
|
111
|
+
Cái giá của việc bỏ qua đã đo được: `DataTable` mọc `pin: "end"` nơi antd có
|
|
112
|
+
`fixed`, `sortable: true` nơi antd có `sorter`, và không có câu trả lời nào cho
|
|
113
|
+
filter/expandable — mỗi lần một người quyết một kiểu. Cùng hình dạng lỗi:
|
|
114
|
+
`Dialog` + `AlertDialog` từng là 26 export với **12 cặp trùng tên** và **0** phần
|
|
115
|
+
chỉ `AlertDialog` mới có, trong khi antd chỉ có MỘT `Modal` (nguy hiểm là một
|
|
116
|
+
PROP, `okType`). Nay là một họ, `variant` là lối chuẩn, 12 export cũ ở lại vì gỡ
|
|
117
|
+
là breaking. Và cả họ Ant Design X từng tới nửa vời — thiếu `Conversations`,
|
|
118
|
+
`Attachments`, `ThoughtChain`, `Welcome`, `Actions`, nay đã đủ.
|
|
119
|
+
|
|
120
|
+
Ba chỗ cố ý lệch khỏi antd (logical thay `left/right`, từ vựng giá trị của kho
|
|
121
|
+
này, `density` thay `size`) đều **ghi lý do trong DESIGN-AUTHORITY**. Lệch thêm
|
|
122
|
+
thì phải viết vào đó, không lệch lặng lẽ. Và đọc bề mặt antd từ **type đã cài ở
|
|
123
|
+
một checkout khác**, không từ trí nhớ — `antd` đã bị gỡ khỏi devDependencies của
|
|
124
|
+
kho này từ 20.0.0 và `check:no-antd-runtime` canh cho nó không quay lại.
|
|
125
|
+
|
|
104
126
|
### Bước 4 — Kiểm bằng tarball TRƯỚC khi phát hành
|
|
105
127
|
|
|
106
128
|
Đây là bước làm cho "vừa làm vừa trải nghiệm" thành thật. Đừng phát hành rồi
|
|
@@ -303,15 +325,22 @@ không tồn tại nên không hỏi**.
|
|
|
303
325
|
Trước khi viết bất kỳ bố cục nào: `search_components` + `get_component`. Rẻ hơn
|
|
304
326
|
mọi lần sửa sau.
|
|
305
327
|
|
|
306
|
-
**
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
328
|
+
**Catalog từng chở PROP mà không chở LUẬT BỐ CỤC — và ví dụ ấy nay đã được vá,
|
|
329
|
+
nên đừng trích nó nữa.** Bản trước của mục này viết: `CardBar` trong manifest có
|
|
330
|
+
đúng một prop (`extra`), không dòng nào nói nó tự lấy đường kẻ theo VỊ TRÍ. Đo
|
|
331
|
+
lại hôm nay: `CardBar` có **6 prop**, trong đó `border` (`"none" | "block-start"
|
|
332
|
+
| "block-end" | "both"`) ép được đường kẻ khi xếp chồng, và luật vị trí (đầu: kẻ
|
|
333
|
+
dưới · cuối: kẻ trên · giữa: cả hai) nằm **trong chính catalog** — ở `usage` của
|
|
334
|
+
`CardBar` lẫn `description` của `Card`.
|
|
335
|
+
|
|
336
|
+
Bài học còn lại vẫn thật, chỉ đổi hình: một luật bố cục **có thể** chỉ sống
|
|
337
|
+
trong chú thích CSS, và không có cổng nào bắt nó phải lên catalog. Nên:
|
|
338
|
+
|
|
339
|
+
1. Hỏi `get_component` trước — nay nó thường trả lời cả hình dạng lẫn luật.
|
|
340
|
+
2. `usage`/`description` im lặng về bố cục → mở `src/styles/*-layout.css` của
|
|
341
|
+
component ấy ra đọc (chỗ định nghĩa hai nhịp `section` và `band` chẳng hạn).
|
|
342
|
+
3. Là chuột bạch, khi bước 2 phải dùng tới, đó là **khoảng trống catalog** — đưa
|
|
343
|
+
luật ấy lên `usage` trong cùng PR, theo §4. Đó là cách `CardBar` được vá.
|
|
315
344
|
|
|
316
345
|
## 6. Thứ KHÔNG đẩy lên DS
|
|
317
346
|
|
package/scripts/ui-audit.mjs
CHANGED
|
@@ -1006,7 +1006,7 @@ function staleOwnedRules() {
|
|
|
1006
1006
|
`This file is written by @godxjp/ui and says version ${stamped}, but the installed package ` +
|
|
1007
1007
|
`is ${installed}. Its rules describe a different library than the one you are building ` +
|
|
1008
1008
|
"against — most likely because `ignore-scripts=true` kept our postinstall from running.",
|
|
1009
|
-
replacement:
|
|
1009
|
+
replacement: "npx @godxjp/ui sync-rules",
|
|
1010
1010
|
snippet: `<!-- godxjp-ui:version ${stamped} --> vs installed ${installed}`,
|
|
1011
1011
|
};
|
|
1012
1012
|
}
|