@godxjp/ui 33.2.1 → 33.3.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.
Files changed (36) hide show
  1. package/README.md +4 -4
  2. package/agent/START-HERE.md +3 -3
  3. package/agent/components/ResponsiveGrid.json +2 -2
  4. package/agent/components/SearchForm.json +115 -0
  5. package/agent/components/Toolbar.json +6 -3
  6. package/agent/components-index.json +10 -1
  7. package/agent/components.json +123 -5
  8. package/agent/index.json +5 -5
  9. package/agent/llms.txt +6 -6
  10. package/agent/patterns/condition-builder.json +31 -0
  11. package/agent/patterns/data-table-page.json +1 -1
  12. package/agent/patterns/filter-bar-layouts.json +30 -0
  13. package/agent/patterns/inertia-list-page.json +1 -1
  14. package/agent/patterns/list-filters.json +1 -1
  15. package/agent/patterns/search-form.json +27 -0
  16. package/agent/patterns-index.json +85 -0
  17. package/agent/patterns.json +91 -3
  18. package/agent/rules.json +1 -1
  19. package/dist/components/navigation/index.d.ts +2 -0
  20. package/dist/components/navigation/index.js +2 -0
  21. package/dist/components/navigation/search-form.d.ts +31 -0
  22. package/dist/components/navigation/search-form.js +112 -0
  23. package/dist/contracts/measurement.json +1 -1
  24. package/dist/i18n/messages/en.json +9 -0
  25. package/dist/i18n/messages/ja.json +9 -0
  26. package/dist/i18n/messages/vi.json +9 -0
  27. package/dist/props/components/layout.prop.d.ts +3 -1
  28. package/dist/props/components/navigation.prop.d.ts +43 -3
  29. package/dist/props/registry.d.ts +5 -0
  30. package/dist/props/registry.js +12 -0
  31. package/dist/styles/layers.json +11 -1
  32. package/dist/styles/layout.css +41 -0
  33. package/dist/styles.css +1 -1
  34. package/docs/CONSUMER-RULES.md +1 -1
  35. package/package.json +2 -2
  36. package/scripts/ui-audit.mjs +148 -3
@@ -0,0 +1,31 @@
1
+ import * as React from "react";
2
+ import type { SearchFormProp } from "../../props/components/navigation.prop.js";
3
+ export type { SearchFormProp, SearchFormProp as SearchFormProps };
4
+ /**
5
+ * SearchForm — the submit-driven 検索条件フォーム (Ant Design ProComponents `QueryFilter`).
6
+ *
7
+ * A `search` landmark (`<form role="search">`) holding a grid of labelled conditions, aligned in
8
+ * columns. The conditions past the first row sit behind one 詳細条件を開く / 閉じる toggle that
9
+ * names how many are hidden; Search (検索) submits, Clear (クリア) resets. After a search the
10
+ * conditions the results were searched with, and the result count, are announced in one
11
+ * `role="status"` line. Every label is localized through the library catalogue.
12
+ *
13
+ * Choose it over a live FilterBar when the conditions are many, the query is heavy, or the users
14
+ * expect an explicit 検索 button (Japanese business systems). Hidden conditions stay MOUNTED, so a
15
+ * value typed into one survives collapsing the section.
16
+ */
17
+ export declare const SearchForm: React.ForwardRefExoticComponent<Omit<import("../data-entry/index.js").FormProp, "aria-label" | "asChild" | "children" | "columns" | "onReset" | "role"> & {
18
+ children: import("../../props/index.js").ChildrenProp;
19
+ columns?: import("../../props/components/layout.prop.js").ResponsiveGridColumnsProp;
20
+ visibleFields?: number;
21
+ open?: import("../../props/index.js").OpenProp;
22
+ defaultOpen?: import("../../props/index.js").DefaultOpenProp;
23
+ onOpenChange?: import("../../props/index.js").OnOpenChangeProp;
24
+ onReset?: () => void;
25
+ searchLabel?: import("../../props/index.js").LabelProp;
26
+ resetLabel?: import("../../props/index.js").LabelProp;
27
+ summary?: React.ReactNode;
28
+ resultCount?: number;
29
+ loading?: import("../../props/index.js").PendingProp;
30
+ label?: string;
31
+ } & React.RefAttributes<HTMLFormElement>>;
@@ -0,0 +1,112 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { ChevronDown, ChevronUp } from "lucide-react";
5
+ import { useTranslation } from "../../i18n/use-translation.js";
6
+ import { cn } from "../../lib/utils.js";
7
+ import { Form } from "../data-entry/form.js";
8
+ import { Button } from "../general/button.js";
9
+ import { ResponsiveGrid } from "../layout/responsive-grid.js";
10
+ const DEFAULT_COLUMNS = { base: 1, md: 2, lg: 3 };
11
+ function widestStep(columns) {
12
+ if (typeof columns === "number") return Math.max(1, columns);
13
+ const steps = Object.values(columns ?? DEFAULT_COLUMNS).filter(
14
+ (n) => typeof n === "number"
15
+ );
16
+ return Math.max(1, ...steps);
17
+ }
18
+ const SearchForm = React.forwardRef(function SearchForm2({
19
+ children,
20
+ columns = DEFAULT_COLUMNS,
21
+ visibleFields,
22
+ open,
23
+ defaultOpen = false,
24
+ onOpenChange,
25
+ onReset,
26
+ searchLabel,
27
+ resetLabel,
28
+ summary,
29
+ resultCount,
30
+ loading,
31
+ label,
32
+ className,
33
+ ...props
34
+ }, ref) {
35
+ const { t } = useTranslation();
36
+ const baseId = React.useId();
37
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
38
+ const isOpen = open ?? uncontrolledOpen;
39
+ const fields = React.Children.toArray(children).filter(React.isValidElement);
40
+ const limit = Math.max(1, visibleFields ?? widestStep(columns));
41
+ const hiddenCount = Math.max(0, fields.length - limit);
42
+ const hiddenIds = Array.from({ length: hiddenCount }, (_, i) => `${baseId}-more-${i}`);
43
+ const toggle = () => {
44
+ const next = !isOpen;
45
+ if (open === void 0) setUncontrolledOpen(next);
46
+ onOpenChange?.(next);
47
+ };
48
+ const showMeta = summary != null || resultCount !== void 0;
49
+ return /* @__PURE__ */ jsx(
50
+ Form,
51
+ {
52
+ ref,
53
+ role: "search",
54
+ "aria-label": label ?? t("navigation.searchForm.label"),
55
+ className: cn("ui-search-form", className),
56
+ ...props,
57
+ children: /* @__PURE__ */ jsxs("div", { className: "ui-search-form-body", children: [
58
+ /* @__PURE__ */ jsx(ResponsiveGrid, { columns, children: fields.map(
59
+ (field, index) => index < limit ? field : (
60
+ /* Past the first row: kept mounted and hidden, so a typed value survives a collapse. */
61
+ /* @__PURE__ */ jsx(
62
+ "div",
63
+ {
64
+ id: hiddenIds[index - limit],
65
+ hidden: !isOpen,
66
+ className: "ui-search-form-more",
67
+ children: field
68
+ },
69
+ field.key ?? index
70
+ )
71
+ )
72
+ ) }),
73
+ /* @__PURE__ */ jsxs("div", { className: "ui-search-form-actions", children: [
74
+ hiddenCount > 0 && /* @__PURE__ */ jsxs(
75
+ Button,
76
+ {
77
+ type: "button",
78
+ variant: "ghost",
79
+ size: "sm",
80
+ className: "ui-search-form-toggle",
81
+ "aria-expanded": isOpen,
82
+ "aria-controls": hiddenIds.join(" "),
83
+ onClick: toggle,
84
+ count: isOpen ? void 0 : hiddenCount,
85
+ showZero: false,
86
+ countLabel: t("navigation.searchForm.hiddenCount"),
87
+ children: [
88
+ isOpen ? t("navigation.searchForm.collapse") : t("navigation.searchForm.expand"),
89
+ isOpen ? /* @__PURE__ */ jsx(ChevronUp, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true" })
90
+ ]
91
+ }
92
+ ),
93
+ /* @__PURE__ */ jsxs("div", { className: "ui-search-form-submit", children: [
94
+ onReset && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: onReset, children: resetLabel ?? t("navigation.searchForm.reset") }),
95
+ /* @__PURE__ */ jsx(Button, { type: "submit", loading, children: searchLabel ?? t("navigation.searchForm.search") })
96
+ ] })
97
+ ] }),
98
+ showMeta && /* @__PURE__ */ jsxs("p", { role: "status", className: "ui-search-form-meta", children: [
99
+ summary != null && /* @__PURE__ */ jsxs("span", { className: "ui-search-form-summary", children: [
100
+ /* @__PURE__ */ jsx("span", { className: "ui-search-form-summary-label", children: t("navigation.searchForm.summary") }),
101
+ " ",
102
+ summary
103
+ ] }),
104
+ resultCount !== void 0 && /* @__PURE__ */ jsx("span", { className: "ui-search-form-count", children: t("navigation.filterBar.resultCount", { count: resultCount }) })
105
+ ] })
106
+ ] })
107
+ }
108
+ );
109
+ });
110
+ export {
111
+ SearchForm
112
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "33.2.1",
3
+ "version": "33.3.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -588,6 +588,15 @@
588
588
  "other": "{count} results"
589
589
  }
590
590
  },
591
+ "searchForm": {
592
+ "label": "Search conditions",
593
+ "search": "Search",
594
+ "reset": "Clear",
595
+ "expand": "More conditions",
596
+ "collapse": "Fewer conditions",
597
+ "hiddenCount": "hidden conditions",
598
+ "summary": "Searched with:"
599
+ },
591
600
  "localePicker": {
592
601
  "ariaLabel": "Language"
593
602
  },
@@ -574,6 +574,15 @@
574
574
  "removeFilter": "フィルターを解除: {label}",
575
575
  "resultCount": "{count} 件の結果"
576
576
  },
577
+ "searchForm": {
578
+ "label": "検索条件",
579
+ "search": "検索",
580
+ "reset": "クリア",
581
+ "expand": "詳細条件を開く",
582
+ "collapse": "詳細条件を閉じる",
583
+ "hiddenCount": "件の条件が非表示",
584
+ "summary": "検索条件:"
585
+ },
577
586
  "localePicker": {
578
587
  "ariaLabel": "言語"
579
588
  },
@@ -575,6 +575,15 @@
575
575
  "removeFilter": "Bỏ bộ lọc: {label}",
576
576
  "resultCount": "{count} kết quả"
577
577
  },
578
+ "searchForm": {
579
+ "label": "Điều kiện tìm kiếm",
580
+ "search": "Tìm kiếm",
581
+ "reset": "Xoá điều kiện",
582
+ "expand": "Mở thêm điều kiện",
583
+ "collapse": "Thu gọn điều kiện",
584
+ "hiddenCount": "điều kiện đang ẩn",
585
+ "summary": "Điều kiện tìm kiếm:"
586
+ },
578
587
  "localePicker": {
579
588
  "ariaLabel": "Ngôn ngữ"
580
589
  },
@@ -401,9 +401,11 @@ export type ResponsiveGridPresetProp = "pricing-plans";
401
401
  * Block-axis alignment of the cells in each grid row — antd `Row align`, spelled with Flex's
402
402
  * `FlexAlignProp` values (antd `top` → `start`; `stretch` is the same word). `stretch` gives every
403
403
  * cell the height of the tallest one, so an empty Kanban lane is still a full-height drop zone.
404
+ * `center` is antd `middle`: cells of different heights share one centre line (a condition row's
405
+ * number and +/− buttons beside taller controls).
404
406
  * Omitted, each flow keeps its own alignment: `columns` → `start`, `rows` → the grid's stretch.
405
407
  */
406
- export type ResponsiveGridAlignProp = Extract<FlexAlignProp, "start" | "stretch">;
408
+ export type ResponsiveGridAlignProp = Extract<FlexAlignProp, "start" | "center" | "stretch">;
407
409
  export type MasterDetailRailWidthProp = "narrow" | "compact" | "standard" | "wide";
408
410
  export type MasterDetailRailProp = "master" | "detail";
409
411
  /**
@@ -1,9 +1,9 @@
1
1
  /** Navigation component prop types — @see docs/COMPONENTS.md#navigation */
2
2
  import type * as React from "react";
3
3
  import type { ReactNode } from "react";
4
- import type { AffixProp, AffixTargetProp } from "./layout.prop.js";
5
- import type { ActionsProp, ChildrenProp, ClassNameProp, DisabledProp, ErrorProp, HasActiveFiltersProp, IdProp, LabelProp, OnClearFiltersProp, PendingProp, PlaceholderProp, StickyProp, TextAlignProp } from "../vocabulary/index.js";
6
- import type { SearchSelectOptionProp } from "./data-entry.prop.js";
4
+ import type { AffixProp, AffixTargetProp, ResponsiveGridColumnsProp } from "./layout.prop.js";
5
+ import type { ActionsProp, ChildrenProp, ClassNameProp, DisabledProp, ErrorProp, HasActiveFiltersProp, IdProp, LabelProp, OnClearFiltersProp, OnOpenChangeProp, OpenProp, DefaultOpenProp, PendingProp, PlaceholderProp, StickyProp, TextAlignProp } from "../vocabulary/index.js";
6
+ import type { FormProp, SearchSelectOptionProp } from "./data-entry.prop.js";
7
7
  /**
8
8
  * Renders the canonical SearchInput as the FIRST control of the strip, with a token-owned
9
9
  * consistent width (`--filter-bar-search-width`, full-width below 640px). Controlled by consumer
@@ -123,6 +123,46 @@ export type ToolbarProp = {
123
123
  export type FilterBarViewsPlacementProp = "above" | "inline";
124
124
  /** @see ToolbarProp.clearDisplay */
125
125
  export type FilterBarClearDisplayProp = "label" | "icon";
126
+ /**
127
+ * @see SearchForm — the submit-driven 検索条件フォーム (Ant Design ProComponents `QueryFilter`): a
128
+ * grid of labelled conditions, an explicit Search (検索) + Clear (クリア) pair, the conditions past
129
+ * the first row behind one 詳細条件を開く toggle, and a 検索条件 summary + result count after the
130
+ * search. A live FilterBar applies each change at once; a SearchForm applies them on submit.
131
+ */
132
+ export type SearchFormProp = Omit<FormProp, "role" | "onReset" | "children" | "columns" | "asChild" | "aria-label"> & {
133
+ /** One FormField per condition, in reading order. Each direct child is one grid cell. */
134
+ children: ChildrenProp;
135
+ /** Grid columns per breakpoint. Default `{ base: 1, md: 2, lg: 3 }`: one column on a phone. */
136
+ columns?: ResponsiveGridColumnsProp;
137
+ /**
138
+ * How many conditions stay visible while the detail section is closed (antd
139
+ * `defaultFormItemsNumber`). Defaults to the widest `columns` step: the first row.
140
+ */
141
+ visibleFields?: number;
142
+ /** Whether the conditions past `visibleFields` are shown (controlled). */
143
+ open?: OpenProp;
144
+ /** Initial open state when uncontrolled. Default `false`: collapsed to the first row. */
145
+ defaultOpen?: DefaultOpenProp;
146
+ /** Fires when the 詳細条件を開く / 閉じる toggle is pressed. */
147
+ onOpenChange?: OnOpenChangeProp;
148
+ /** Clear (クリア) handler. The Clear button renders only when this is set. */
149
+ onReset?: () => void;
150
+ /** Overrides the localized Search (検索) button text. */
151
+ searchLabel?: LabelProp;
152
+ /** Overrides the localized Clear (クリア) button text. */
153
+ resetLabel?: LabelProp;
154
+ /**
155
+ * The conditions the CURRENT results were searched with, shown after the localized 検索条件
156
+ * label in a `role="status"` line (e.g. 「国: ベトナム / 状態: 稼働中」).
157
+ */
158
+ summary?: ReactNode;
159
+ /** Localized, pluralized result count shown in the same status line. */
160
+ resultCount?: number;
161
+ /** Puts the Search button in its loading state while the query runs. */
162
+ loading?: PendingProp;
163
+ /** Accessible name of the `search` landmark. Defaults to a localized "Search conditions". */
164
+ label?: string;
165
+ };
126
166
  /** @see ToolbarGroup */
127
167
  export type ToolbarGroupProp = {
128
168
  label: LabelProp;
@@ -2692,6 +2692,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
2692
2692
  readonly file: "components/navigation.prop.ts";
2693
2693
  readonly vocabulary: readonly ["OnClearFiltersProp", "HasActiveFiltersProp", "StickyProp", "ActionsProp", "PendingProp", "DisabledProp", "ErrorProp", "ClassNameProp", "ChildrenProp"];
2694
2694
  };
2695
+ readonly SearchFormProp: {
2696
+ readonly group: "navigation";
2697
+ readonly file: "components/navigation.prop.ts";
2698
+ readonly vocabulary: readonly ["OpenProp", "DefaultOpenProp", "OnOpenChangeProp", "LabelProp", "PendingProp", "ChildrenProp"];
2699
+ };
2695
2700
  readonly FilterBarClearDisplayProp: {
2696
2701
  readonly group: "navigation";
2697
2702
  readonly file: "components/navigation.prop.ts";
@@ -3159,6 +3159,18 @@ const COMPONENT_PROP_REGISTRY = {
3159
3159
  "ChildrenProp"
3160
3160
  ]
3161
3161
  },
3162
+ SearchFormProp: {
3163
+ group: "navigation",
3164
+ file: "components/navigation.prop.ts",
3165
+ vocabulary: [
3166
+ "OpenProp",
3167
+ "DefaultOpenProp",
3168
+ "OnOpenChangeProp",
3169
+ "LabelProp",
3170
+ "PendingProp",
3171
+ "ChildrenProp"
3172
+ ]
3173
+ },
3162
3174
  FilterBarClearDisplayProp: {
3163
3175
  group: "navigation",
3164
3176
  file: "components/navigation.prop.ts",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "33.2.1",
2
+ "version": "33.3.0",
3
3
  "generatedBy": "scripts/gen-style-layers.mjs — do not edit by hand; run `pnpm gen:style-layers`",
4
4
  "base": "base.css",
5
5
  "fonts": "fonts.css",
@@ -853,6 +853,16 @@
853
853
  "data-display-layout.css"
854
854
  ]
855
855
  },
856
+ "SearchForm": {
857
+ "layers": [
858
+ "layout.css",
859
+ "control.css",
860
+ "text-layout.css",
861
+ "dialog-layout.css",
862
+ "alert-layout.css",
863
+ "form-layout.css"
864
+ ]
865
+ },
856
866
  "SearchInput": {
857
867
  "layers": [
858
868
  "control.css"
@@ -431,6 +431,10 @@
431
431
  align-items: stretch;
432
432
  }
433
433
 
434
+ .ui-responsive-grid[data-align="center"] {
435
+ align-items: center;
436
+ }
437
+
434
438
  .ui-responsive-grid[data-gap="none"] {
435
439
  gap: 0;
436
440
  }
@@ -1580,6 +1584,43 @@
1580
1584
  color: hsl(var(--text-error));
1581
1585
  }
1582
1586
 
1587
+ .ui-search-form-body {
1588
+ display: flex;
1589
+ flex-direction: column;
1590
+ gap: var(--filter-bar-section-gap);
1591
+ }
1592
+
1593
+ .ui-search-form-more {
1594
+ min-inline-size: 0;
1595
+ }
1596
+
1597
+ .ui-search-form-actions {
1598
+ display: flex;
1599
+ flex-wrap: wrap;
1600
+ align-items: center;
1601
+ gap: var(--filter-bar-gap);
1602
+ }
1603
+
1604
+ .ui-search-form-submit {
1605
+ display: flex;
1606
+ flex-wrap: wrap;
1607
+ align-items: center;
1608
+ gap: var(--space-2);
1609
+ margin-inline-start: auto;
1610
+ }
1611
+
1612
+ .ui-search-form-meta {
1613
+ display: flex;
1614
+ flex-wrap: wrap;
1615
+ gap: var(--space-1) var(--space-3);
1616
+ font-size: var(--filter-label-font-size);
1617
+ color: hsl(var(--muted-foreground));
1618
+ }
1619
+
1620
+ .ui-search-form-summary-label {
1621
+ font-weight: var(--font-weight-medium);
1622
+ }
1623
+
1583
1624
  .ui-legal-document-scope {
1584
1625
  container: legal-document / inline-size;
1585
1626
  padding-inline: var(--legal-document-page-padding-inline, var(--space-page-x));