@godxjp/ui 30.8.0 → 31.0.2
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 +25 -2
- package/agent/START-HERE.md +1 -1
- package/agent/components/PrefetchLink.json +3 -3
- package/agent/components-index.json +1 -1
- package/agent/components.json +3 -3
- package/agent/index.json +2 -2
- package/agent/llms.txt +3 -3
- package/agent/patterns/settings-page-responsive.json +1 -1
- package/agent/patterns.json +1 -1
- package/dist/components/query/index.d.ts +0 -2
- package/dist/components/query/index.js +0 -2
- package/dist/components/react-router/index.d.ts +2 -0
- package/dist/components/react-router/index.js +4 -0
- package/dist/contracts/measurement.json +1 -1
- package/dist/styles/alert-layout.css +4 -2
- package/dist/styles/layers.json +1182 -0
- package/dist/styles/vendor-day-picker.css +2 -0
- package/dist/styles/vendor-sonner.css +2 -0
- package/docs/CONSUMER-RULES.md +1 -1
- package/docs/data-display/permission-matrix.tsx +30 -28
- package/docs/data-entry/cascader.tsx +22 -20
- package/docs/data-entry/date-picker.tsx +47 -43
- package/docs/data-entry/form-field/examples/a11y-contract.tsx +80 -76
- package/docs/data-entry/form-field/examples/create-form.tsx +33 -31
- package/docs/data-entry/form-field/index.tsx +61 -57
- package/docs/data-entry/form.tsx +112 -106
- package/docs/data-entry/input.tsx +297 -270
- package/docs/data-entry/label.tsx +11 -9
- package/docs/data-entry/number-input.tsx +86 -78
- package/docs/data-entry/radio-group.tsx +33 -26
- package/docs/data-entry/rating.tsx +29 -25
- package/docs/data-entry/select-matrix.tsx +49 -47
- package/docs/data-entry/select.tsx +38 -35
- package/docs/data-entry/slider.tsx +45 -41
- package/docs/data-entry/textarea.tsx +44 -42
- package/docs/data-entry/time-picker.tsx +51 -47
- package/docs/feedback/sheet.tsx +84 -79
- package/docs/foundation/theme-editor.tsx +133 -124
- package/docs/general/button/examples/form-actions.tsx +17 -15
- package/docs/layout/auth-recovery/examples/password-recovery.tsx +31 -29
- package/docs/layout/auth-shell-registration.tsx +75 -72
- package/docs/layout/auth-shell-variants.tsx +55 -51
- package/docs/layout/space-compact.tsx +11 -8
- package/docs/{query → react-router}/prefetch-link.tsx +1 -1
- package/docs/showcase/caimono-price-comparison.tsx +59 -51
- package/docs/showcase/case3-approval-workflow.tsx +27 -25
- package/docs/showcase/case4-login.tsx +37 -34
- package/docs/showcase/permission-matrix.tsx +44 -42
- package/docs/showcase/theme-customization.tsx +108 -99
- package/package.json +10 -3
- package/scripts/cli.mjs +15 -0
- package/scripts/consumer-rule.md +14 -0
- package/scripts/prune-css.mjs +221 -0
- package/scripts/ui-audit.mjs +99 -2
- /package/dist/components/{query → react-router}/prefetch-link.d.ts +0 -0
- /package/dist/components/{query → react-router}/prefetch-link.js +0 -0
package/scripts/ui-audit.mjs
CHANGED
|
@@ -85,8 +85,6 @@ function changedFiles() {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
88
|
const parts = [
|
|
91
89
|
run(["diff", "--name-only", "--diff-filter=ACMR", mergeBase.trim(), "--"]),
|
|
92
90
|
run(["diff", "--name-only", "--diff-filter=ACMR", "--cached"]),
|
|
@@ -1895,6 +1893,105 @@ for (const dir of SCAN_DIRS) {
|
|
|
1895
1893
|
snippet: match[0].replace(/\s+/g, " ").slice(0, 120),
|
|
1896
1894
|
});
|
|
1897
1895
|
}
|
|
1896
|
+
|
|
1897
|
+
/*
|
|
1898
|
+
* THE LAYER ABOVE THE FIELD (gh#998). `bare-control-needs-formfield` stops a control without a
|
|
1899
|
+
* FormField; nothing stopped a FormField without a Form, or a Form without a width, or a form
|
|
1900
|
+
* that belongs on a page stuffed into a Dialog. godx-mailer hit all four in one day. Validated
|
|
1901
|
+
* on the reporter's own files before and after its fixes: every defect caught before, none in
|
|
1902
|
+
* the fixed files except one the fix missed (four FormFields in a hand-rolled wrapping Flex).
|
|
1903
|
+
*/
|
|
1904
|
+
if (isJsx) {
|
|
1905
|
+
const lineAt = (i) => scanContent.slice(0, i).split("\n").length;
|
|
1906
|
+
/*
|
|
1907
|
+
* TWO OR MORE FormFields under ONE parent with no <Form> above them. That is the shape of
|
|
1908
|
+
* every defect in the report — a hand-rolled <Flex> row of fields, a long form in a Dialog, a
|
|
1909
|
+
* page of fields each choosing its own layout. A LONE field is not: a search box, the
|
|
1910
|
+
* ConfirmDialog's type-to-confirm input, a field component whose whole output is one
|
|
1911
|
+
* FormField. Flagging those made this repo's own library code and 29 docs pages red on a
|
|
1912
|
+
* rule whose defect they do not have.
|
|
1913
|
+
*
|
|
1914
|
+
* The walk keeps a stack of open JSX elements so "same parent" is structural. `(?<![\w.$])`
|
|
1915
|
+
* skips a generic argument (`useRef<HTMLDivElement>`), which is preceded by an identifier.
|
|
1916
|
+
*/
|
|
1917
|
+
const stack = [];
|
|
1918
|
+
const bareByParent = new Map();
|
|
1919
|
+
let formDepth = 0;
|
|
1920
|
+
for (const m of scanContent.matchAll(
|
|
1921
|
+
/(?<![\w.$])<(\/?)([A-Za-z][\w.]*)(?=[\s>/])([^<>]*?(?:\{[^{}]*\}[^<>]*?)*)(\/?)>/g,
|
|
1922
|
+
)) {
|
|
1923
|
+
const [, close, tag, , selfClose] = m;
|
|
1924
|
+
if (close) {
|
|
1925
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
1926
|
+
if (stack[i].tag === tag) {
|
|
1927
|
+
stack.length = i;
|
|
1928
|
+
break;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
if (tag === "Form") formDepth = Math.max(0, formDepth - 1);
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
if (tag === "FormField" && formDepth === 0) {
|
|
1935
|
+
const parent = stack.at(-1)?.index ?? -1;
|
|
1936
|
+
(bareByParent.get(parent) ?? bareByParent.set(parent, []).get(parent)).push(m.index);
|
|
1937
|
+
}
|
|
1938
|
+
if (selfClose) continue;
|
|
1939
|
+
stack.push({ tag, index: m.index });
|
|
1940
|
+
if (tag === "Form") formDepth += 1;
|
|
1941
|
+
}
|
|
1942
|
+
for (const group of bareByParent.values()) {
|
|
1943
|
+
if (group.length < 2) continue;
|
|
1944
|
+
for (const at of group) {
|
|
1945
|
+
const lineNo = lineAt(at);
|
|
1946
|
+
if (suppressed("formfield-needs-form", lineNo - 1)) continue;
|
|
1947
|
+
findings.push({
|
|
1948
|
+
file: rel,
|
|
1949
|
+
line: lineNo,
|
|
1950
|
+
rule: "formfield-needs-form",
|
|
1951
|
+
severity: "error",
|
|
1952
|
+
standard: "@godxjp/ui Form (gh#998)",
|
|
1953
|
+
message: `${group.length} FormFields under one parent with no <Form> around them. <Form layout="horizontal" labelWidth controlWidth> is what lines fields up — without it each form picks its own layout, and a row of fields in a hand-rolled <Flex> drops the required field's label out of line. Wrap them in <Form>; a row that belongs together is <SpaceCompact> or <Form columns>. A lone field (a search box, a type-to-confirm input) is not flagged.`,
|
|
1954
|
+
snippet: scanContent.slice(at, at + 120).replace(/\s+/g, " "),
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
for (const m of scanContent.matchAll(/<(Dialog\.Body|DialogBody)(?=[\s>])[\s\S]*?<\/\1>/g)) {
|
|
1960
|
+
const count = (m[0].match(/<FormField(?=[\s>/])/g) ?? []).length;
|
|
1961
|
+
if (count < 3) continue;
|
|
1962
|
+
const lineNo = lineAt(m.index);
|
|
1963
|
+
if (suppressed("dialog-form-too-big", lineNo - 1)) continue;
|
|
1964
|
+
findings.push({
|
|
1965
|
+
file: rel,
|
|
1966
|
+
line: lineNo,
|
|
1967
|
+
rule: "dialog-form-too-big",
|
|
1968
|
+
severity: "error",
|
|
1969
|
+
standard: "@godxjp/ui form placement (gh#998)",
|
|
1970
|
+
message: `${count} FormFields in a ${m[1]} — a form this size is a page of its own (its own route, back button, and room to scroll), not a modal. Keep dialogs for a confirmation or one or two fields. A Sheet is not flagged: a side drawer is where an advanced filter or an edit form belongs (antd Drawer).`,
|
|
1971
|
+
snippet: m[0].slice(0, 120).replace(/\s+/g, " "),
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
for (const m of scanContent.matchAll(/<FormField(?=[\s>])([^>]*)>([\s\S]*?)<\/FormField>/g)) {
|
|
1976
|
+
if (!/<Select(?=[\s>/])/.test(m[2]) || /controlWidth=/.test(m[1])) continue;
|
|
1977
|
+
const before = scanContent.slice(0, m.index);
|
|
1978
|
+
const lastForm = [...before.matchAll(/<Form(?=[\s>])([^>]*)>/g)].at(-1);
|
|
1979
|
+
const insideForm = lastForm && before.lastIndexOf("</Form>") < lastForm.index;
|
|
1980
|
+
if (insideForm && /controlWidth=/.test(lastForm[1])) continue;
|
|
1981
|
+
const lineNo = lineAt(m.index);
|
|
1982
|
+
if (suppressed("select-width-hint", lineNo - 1)) continue;
|
|
1983
|
+
findings.push({
|
|
1984
|
+
file: rel,
|
|
1985
|
+
line: lineNo,
|
|
1986
|
+
rule: "select-width-hint",
|
|
1987
|
+
severity: "warn",
|
|
1988
|
+
standard: "GOV.UK Design System · text input width",
|
|
1989
|
+
message:
|
|
1990
|
+
"A <Select> in a FormField with no controlWidth on the field or its <Form> stretches to the full column — a two-option select as wide as an address line. Size the control for its content (`controlWidth` on the FormField, or once on the Form). https://design-system.service.gov.uk/components/text-input/",
|
|
1991
|
+
snippet: scanContent.slice(m.index, m.index + 120).replace(/\s+/g, " "),
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1898
1995
|
}
|
|
1899
1996
|
}
|
|
1900
1997
|
|
|
File without changes
|
|
File without changes
|