@godxjp/ui 32.8.0 → 32.9.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 (39) hide show
  1. package/DESIGN.md +10 -3
  2. package/README.md +5 -5
  3. package/agent/DESIGN.md +10 -3
  4. package/agent/START-HERE.md +9 -7
  5. package/agent/components/AppShell.json +1 -0
  6. package/agent/components/BlockEditor.json +1 -0
  7. package/agent/components/PageContainer.json +1 -0
  8. package/agent/components/SearchInput.json +1 -0
  9. package/agent/components/Topbar.json +2 -0
  10. package/agent/components/TopbarItem.json +1 -0
  11. package/agent/components/TopbarSearch.json +49 -0
  12. package/agent/components-index.json +5 -0
  13. package/agent/components.json +56 -0
  14. package/agent/index.json +7 -7
  15. package/agent/llms.txt +7 -7
  16. package/agent/tokens.json +38 -2
  17. package/dist/components/layout/index.d.ts +2 -0
  18. package/dist/components/layout/index.js +2 -0
  19. package/dist/components/layout/topbar-search.d.ts +12 -0
  20. package/dist/components/layout/topbar-search.js +87 -0
  21. package/dist/contracts/measurement.json +1 -1
  22. package/dist/i18n/messages/en.json +1 -0
  23. package/dist/i18n/messages/ja.json +1 -0
  24. package/dist/i18n/messages/vi.json +1 -0
  25. package/dist/props/components/layout.prop.d.ts +22 -0
  26. package/dist/props/registry.d.ts +9 -0
  27. package/dist/props/registry.js +16 -0
  28. package/dist/styles/card-layout.css +1 -1
  29. package/dist/styles/data-display-layout.css +19 -0
  30. package/dist/styles/layers.json +7 -1
  31. package/dist/styles/shell-layout.css +123 -2
  32. package/dist/styles.css +1 -1
  33. package/dist/tokens/components/card.css +3 -1
  34. package/dist/tokens/components/data-display.css +2 -0
  35. package/dist/tokens/components/shell.css +7 -0
  36. package/package.json +2 -2
  37. package/scripts/_agent-setup.mjs +10 -5
  38. package/scripts/cli.mjs +12 -6
  39. package/scripts/visual-audit.mjs +1367 -90
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ import type { TopbarSearchProp } from "../../props/components/layout.prop.js";
3
+ export type { TopbarSearchProp, TopbarSearchProp as TopbarSearchProps, } from "../../props/components/layout.prop.js";
4
+ /**
5
+ * TopbarSearch — the bar's search box, folded to a named search cell below `collapseBelow` (ADR C02).
6
+ *
7
+ * WAI-ARIA APG disclosure: the cell is a button with `aria-expanded` / `aria-controls`; the panel it
8
+ * shows holds the ONE `SearchInput` this component renders. Which of the two is on screen is decided
9
+ * by CSS at the step (`data-collapse-below`), not by a media-query hook, so SSR and the first client
10
+ * paint agree and the field keeps its value across a resize.
11
+ */
12
+ export declare function TopbarSearch({ collapseBelow, className, ...props }: TopbarSearchProp): React.JSX.Element;
@@ -0,0 +1,87 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Search, X } from "lucide-react";
5
+ import { useTranslation } from "../../i18n/use-translation.js";
6
+ import { cn } from "../../lib/utils.js";
7
+ import { SearchInput } from "../data-entry/search-input.js";
8
+ import { TopbarItem } from "./topbar-item.js";
9
+ function TopbarSearch({ collapseBelow = "sm", className, ...props }) {
10
+ const { t } = useTranslation();
11
+ const [open, setOpen] = React.useState(false);
12
+ const triggerRef = React.useRef(null);
13
+ const panelRef = React.useRef(null);
14
+ const pressedInside = React.useRef(false);
15
+ const panelId = React.useId();
16
+ const name = props.ariaLabel ?? t("common.search");
17
+ React.useEffect(() => {
18
+ if (open) panelRef.current?.querySelector("input")?.focus();
19
+ }, [open]);
20
+ const close = (returnFocus) => {
21
+ setOpen(false);
22
+ if (returnFocus) triggerRef.current?.focus();
23
+ };
24
+ return /* @__PURE__ */ jsxs(
25
+ "div",
26
+ {
27
+ "data-slot": "topbar-search",
28
+ "data-collapse-below": collapseBelow,
29
+ "data-open": open ? "true" : void 0,
30
+ className: cn("ui-topbar-search", className),
31
+ children: [
32
+ /* @__PURE__ */ jsx(
33
+ TopbarItem,
34
+ {
35
+ ref: triggerRef,
36
+ className: "ui-topbar-search-trigger",
37
+ "aria-label": name,
38
+ "aria-expanded": open,
39
+ "aria-controls": panelId,
40
+ icon: /* @__PURE__ */ jsx(Search, { "aria-hidden": "true" }),
41
+ onClick: () => setOpen(true)
42
+ }
43
+ ),
44
+ /* @__PURE__ */ jsxs(
45
+ "div",
46
+ {
47
+ ref: panelRef,
48
+ id: panelId,
49
+ className: "ui-topbar-search-panel",
50
+ onKeyDown: (event) => {
51
+ if (open && event.key === "Escape" && !event.nativeEvent.isComposing) {
52
+ event.preventDefault();
53
+ close(true);
54
+ }
55
+ },
56
+ onPointerDownCapture: () => {
57
+ pressedInside.current = true;
58
+ setTimeout(() => {
59
+ pressedInside.current = false;
60
+ });
61
+ },
62
+ onBlur: (event) => {
63
+ if (open && !pressedInside.current && !event.currentTarget.contains(event.relatedTarget)) {
64
+ close(false);
65
+ }
66
+ },
67
+ children: [
68
+ /* @__PURE__ */ jsx(SearchInput, { ...props }),
69
+ /* @__PURE__ */ jsx(
70
+ TopbarItem,
71
+ {
72
+ className: "ui-topbar-search-close",
73
+ "aria-label": t("layout.topbar.closeSearch"),
74
+ icon: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" }),
75
+ onClick: () => close(true)
76
+ }
77
+ )
78
+ ]
79
+ }
80
+ )
81
+ ]
82
+ }
83
+ );
84
+ }
85
+ export {
86
+ TopbarSearch
87
+ };
@@ -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": "32.8.0",
3
+ "version": "32.9.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,
@@ -340,6 +340,7 @@
340
340
  "more": "More",
341
341
  "search": "Search",
342
342
  "searchPlaceholder": "Search...",
343
+ "closeSearch": "Close search",
343
344
  "notifications": "Notifications",
344
345
  "tweaks": "Open settings"
345
346
  },
@@ -335,6 +335,7 @@
335
335
  "more": "その他",
336
336
  "search": "検索",
337
337
  "searchPlaceholder": "検索...",
338
+ "closeSearch": "検索を閉じる",
338
339
  "notifications": "通知",
339
340
  "tweaks": "設定を開く"
340
341
  },
@@ -336,6 +336,7 @@
336
336
  "more": "Thêm",
337
337
  "search": "Tìm kiếm",
338
338
  "searchPlaceholder": "Tìm kiếm...",
339
+ "closeSearch": "Đóng tìm kiếm",
339
340
  "notifications": "Thông báo",
340
341
  "tweaks": "Mở tùy chỉnh"
341
342
  },
@@ -3,6 +3,7 @@ import type * as React from "react";
3
3
  import type { ComponentType, ReactNode, SVGProps } from "react";
4
4
  import type { BreadcrumbProp, TitleProp, SubtitleProp, StatusProp, ExtraProp, FooterProp, PageDensityProp, DensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, MobileShellHeightProp, MobileShellWidthProp, OrientationProp, TextAlignProp, TextToneProp, BreakpointProp, GapProp, PadProp, PadRawProp, ClassNameProp, ChildrenProp, IdProp, DisabledProp, DescriptionProp, ActionProp, IconProp, HeadingLevelProp, ToneProp, WidthProp, SizeProp } from "../vocabulary/index.js";
5
5
  import type { EmptyStateToneProp } from "./data-display.prop.js";
6
+ import type { SearchInputProp } from "./data-entry.prop.js";
6
7
  /**
7
8
  * Arrangement of the page header's title band and its `extra` slot below the 640px step.
8
9
  * `responsive-inline` keeps `extra` beside the title band at the `--page-header-extra-measure`
@@ -1568,6 +1569,27 @@ export type TopbarProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children">
1568
1569
  /** Escape hatch — render fully custom bar content instead of the three slots. */
1569
1570
  children?: ReactNode;
1570
1571
  };
1572
+ /**
1573
+ * @see TopbarSearch — the bar's one search box, which folds to a named search cell on a narrow bar.
1574
+ *
1575
+ * A `SearchInput` dropped into a `Topbar` slot shrinks with its cluster: at 390px in a consumer bar
1576
+ * its text area held under 3ch, so neither a query nor the placeholder could be read (ADR C02).
1577
+ * Below `collapseBelow` this renders a `TopbarItem` named by `ariaLabel`; activating it opens the
1578
+ * field across the whole bar (Material 3 search bar → search view), focused, with a close cell.
1579
+ * Escape or the close cell folds it back and returns focus to the trigger; focus leaving the open
1580
+ * field folds it too. From the step up it is the inline field, whose text area never shrinks below
1581
+ * `--topbar-search-min-text-inline-size` (8ch).
1582
+ *
1583
+ * Every `SearchInput` prop passes through unchanged. ONE field is rendered at every width, so the
1584
+ * typed query survives a resize and the open/closed switch.
1585
+ */
1586
+ export type TopbarSearchProp = SearchInputProp & {
1587
+ /**
1588
+ * The step below which the field folds to a search cell. Default `"sm"` (40rem) — the bar's own
1589
+ * compact step, shared with `TopbarItem labelHideBelow` and `AppShell logoCompactBelow`.
1590
+ */
1591
+ collapseBelow?: BreakpointProp;
1592
+ };
1571
1593
  /**
1572
1594
  * @see TopbarItem — ONE interactive cell of a {@link TopbarProp} slot: the account button, a
1573
1595
  * settings trigger, a notifications bell.
@@ -1456,6 +1456,15 @@ export declare const COMPONENT_PROP_REGISTRY: {
1456
1456
  readonly reason: "Flex `hideBelow` scoped to the cell's label — icon-only below a breakpoint; visually hidden so the accessible name survives (gh#726).";
1457
1457
  }];
1458
1458
  };
1459
+ readonly TopbarSearchProp: {
1460
+ readonly group: "layout";
1461
+ readonly file: "components/layout.prop.ts";
1462
+ readonly vocabulary: readonly ["LabelProp", "PlaceholderProp", "DisabledProp", "ClassNameProp", "IdProp", {
1463
+ readonly field: "collapseBelow";
1464
+ readonly local: true;
1465
+ readonly reason: "Flex `hideBelow`'s breakpoint vocabulary, naming the step below which the bar's search folds to a search cell (ADR C02).";
1466
+ }];
1467
+ };
1459
1468
  readonly NavListProp: {
1460
1469
  readonly group: "layout";
1461
1470
  readonly file: "components/layout.prop.ts";
@@ -1579,6 +1579,22 @@ const COMPONENT_PROP_REGISTRY = {
1579
1579
  }
1580
1580
  ]
1581
1581
  },
1582
+ TopbarSearchProp: {
1583
+ group: "layout",
1584
+ file: "components/layout.prop.ts",
1585
+ vocabulary: [
1586
+ "LabelProp",
1587
+ "PlaceholderProp",
1588
+ "DisabledProp",
1589
+ "ClassNameProp",
1590
+ "IdProp",
1591
+ {
1592
+ field: "collapseBelow",
1593
+ local: true,
1594
+ reason: "Flex `hideBelow`'s breakpoint vocabulary, naming the step below which the bar's search folds to a search cell (ADR C02)."
1595
+ }
1596
+ ]
1597
+ },
1582
1598
  NavListProp: {
1583
1599
  group: "layout",
1584
1600
  file: "components/layout.prop.ts",
@@ -501,7 +501,7 @@
501
501
  font-weight: var(--stat-card-label-font-weight);
502
502
  letter-spacing: var(--stat-card-label-letter-spacing);
503
503
  line-height: var(--line-height-normal);
504
- text-transform: uppercase;
504
+ text-transform: var(--stat-card-label-letter-case);
505
505
  }
506
506
 
507
507
  [data-slot="stat-card-label"] svg {
@@ -1574,6 +1574,25 @@
1574
1574
  gap: var(--space-1);
1575
1575
  }
1576
1576
 
1577
+ @media (width < 40rem) {
1578
+ .ui-block-editor {
1579
+ padding-inline-start: var(--block-editor-compact-gutter-inline-size);
1580
+ }
1581
+
1582
+ .ui-block-editor-handle {
1583
+ inset-inline: auto var(--block-editor-compact-gutter-inline-size);
1584
+ translate: 0 -100%;
1585
+ padding: var(--stroke-hairline);
1586
+ border: var(--stroke-hairline) solid hsl(var(--border));
1587
+ border-radius: var(--popover-radius, var(--radius));
1588
+ background-color: hsl(var(--popover) / var(--popover-alpha, 100%));
1589
+ }
1590
+
1591
+ .ui-block-editor-handle[data-first-block] {
1592
+ translate: none;
1593
+ }
1594
+ }
1595
+
1577
1596
  @media (hover: hover) {
1578
1597
  .ui-block-editor:not(:hover):not(:focus-within) .ui-block-editor-handle {
1579
1598
  opacity: 0;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "32.8.0",
2
+ "version": "32.9.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",
@@ -1072,6 +1072,12 @@
1072
1072
  "shell-layout.css"
1073
1073
  ]
1074
1074
  },
1075
+ "TopbarSearch": {
1076
+ "layers": [
1077
+ "shell-layout.css",
1078
+ "control.css"
1079
+ ]
1080
+ },
1075
1081
  "Transfer": {
1076
1082
  "layers": [
1077
1083
  "layout.css",
@@ -1653,9 +1653,9 @@
1653
1653
  color: var(--sidebar-section-label-foreground, hsl(var(--muted-foreground)));
1654
1654
  font-size: var(--sidebar-section-label-font-size);
1655
1655
  font-weight: 700;
1656
- letter-spacing: 0.08em;
1656
+ letter-spacing: var(--sidebar-section-label-letter-spacing);
1657
1657
  line-height: 1.2;
1658
- text-transform: uppercase;
1658
+ text-transform: var(--sidebar-section-label-letter-case);
1659
1659
  }
1660
1660
 
1661
1661
  .sb-nav {
@@ -2085,6 +2085,127 @@
2085
2085
  justify-content: center;
2086
2086
  }
2087
2087
 
2088
+ .ui-topbar-search,
2089
+ .ui-topbar-search-panel {
2090
+ display: flex;
2091
+ align-self: stretch;
2092
+ align-items: center;
2093
+ gap: var(--topbar-gap);
2094
+ }
2095
+
2096
+ .ui-topbar-search-panel > .ui-search-input {
2097
+ inline-size: var(--topbar-search-inline-size);
2098
+ flex: 0 1 auto;
2099
+ }
2100
+
2101
+ .ui-topbar-search .ui-search-input-control {
2102
+ min-inline-size: calc(
2103
+ var(--search-input-start-padding) + var(--topbar-search-min-text-inline-size) +
2104
+ max(
2105
+ var(--search-input-end-padding),
2106
+ var(--search-input-end-padding) - var(--control-icon-size) - var(--control-gap) + 6ch
2107
+ )
2108
+ );
2109
+ }
2110
+
2111
+ .app-topbar:has(.ui-topbar-search[data-open]),
2112
+ .ui-topbar:not(.app-topbar .ui-topbar):has(.ui-topbar-search[data-open]) {
2113
+ position: relative;
2114
+ }
2115
+
2116
+ @media (width >= 40rem) {
2117
+ .ui-topbar-search[data-collapse-below="sm"]
2118
+ :is(.ui-topbar-search-trigger, .ui-topbar-search-close) {
2119
+ display: none;
2120
+ }
2121
+ }
2122
+ @media (width >= 48rem) {
2123
+ .ui-topbar-search[data-collapse-below="md"]
2124
+ :is(.ui-topbar-search-trigger, .ui-topbar-search-close) {
2125
+ display: none;
2126
+ }
2127
+ }
2128
+ @media (width >= 64rem) {
2129
+ .ui-topbar-search[data-collapse-below="lg"]
2130
+ :is(.ui-topbar-search-trigger, .ui-topbar-search-close) {
2131
+ display: none;
2132
+ }
2133
+ }
2134
+ @media (width >= 80rem) {
2135
+ .ui-topbar-search[data-collapse-below="xl"]
2136
+ :is(.ui-topbar-search-trigger, .ui-topbar-search-close) {
2137
+ display: none;
2138
+ }
2139
+ }
2140
+ @media (width < 40rem) {
2141
+ .ui-topbar-search[data-collapse-below="sm"]:not([data-open]) .ui-topbar-search-panel {
2142
+ display: none;
2143
+ }
2144
+ .ui-topbar-search[data-collapse-below="sm"][data-open] .ui-topbar-search-panel {
2145
+ position: absolute;
2146
+ z-index: 1;
2147
+ inset: 0;
2148
+ padding-inline: var(--app-shell-bar-inset-compact);
2149
+ background: var(--app-shell-bar-background, hsl(var(--card) / var(--card-alpha, 100%)));
2150
+ }
2151
+ .ui-topbar-search[data-collapse-below="sm"][data-open]
2152
+ .ui-topbar-search-panel
2153
+ > .ui-search-input {
2154
+ flex: 1 1 auto;
2155
+ }
2156
+ }
2157
+ @media (width < 48rem) {
2158
+ .ui-topbar-search[data-collapse-below="md"]:not([data-open]) .ui-topbar-search-panel {
2159
+ display: none;
2160
+ }
2161
+ .ui-topbar-search[data-collapse-below="md"][data-open] .ui-topbar-search-panel {
2162
+ position: absolute;
2163
+ z-index: 1;
2164
+ inset: 0;
2165
+ padding-inline: var(--app-shell-bar-inset-compact);
2166
+ background: var(--app-shell-bar-background, hsl(var(--card) / var(--card-alpha, 100%)));
2167
+ }
2168
+ .ui-topbar-search[data-collapse-below="md"][data-open]
2169
+ .ui-topbar-search-panel
2170
+ > .ui-search-input {
2171
+ flex: 1 1 auto;
2172
+ }
2173
+ }
2174
+ @media (width < 64rem) {
2175
+ .ui-topbar-search[data-collapse-below="lg"]:not([data-open]) .ui-topbar-search-panel {
2176
+ display: none;
2177
+ }
2178
+ .ui-topbar-search[data-collapse-below="lg"][data-open] .ui-topbar-search-panel {
2179
+ position: absolute;
2180
+ z-index: 1;
2181
+ inset: 0;
2182
+ padding-inline: var(--app-shell-bar-inset-compact);
2183
+ background: var(--app-shell-bar-background, hsl(var(--card) / var(--card-alpha, 100%)));
2184
+ }
2185
+ .ui-topbar-search[data-collapse-below="lg"][data-open]
2186
+ .ui-topbar-search-panel
2187
+ > .ui-search-input {
2188
+ flex: 1 1 auto;
2189
+ }
2190
+ }
2191
+ @media (width < 80rem) {
2192
+ .ui-topbar-search[data-collapse-below="xl"]:not([data-open]) .ui-topbar-search-panel {
2193
+ display: none;
2194
+ }
2195
+ .ui-topbar-search[data-collapse-below="xl"][data-open] .ui-topbar-search-panel {
2196
+ position: absolute;
2197
+ z-index: 1;
2198
+ inset: 0;
2199
+ padding-inline: var(--app-shell-bar-inset-compact);
2200
+ background: var(--app-shell-bar-background, hsl(var(--card) / var(--card-alpha, 100%)));
2201
+ }
2202
+ .ui-topbar-search[data-collapse-below="xl"][data-open]
2203
+ .ui-topbar-search-panel
2204
+ > .ui-search-input {
2205
+ flex: 1 1 auto;
2206
+ }
2207
+ }
2208
+
2088
2209
  :is(.ui-topbar-start, .ui-topbar-center, .ui-topbar-end) > *:has(.ui-topbar-item) {
2089
2210
  align-self: stretch;
2090
2211
  }