@godxjp/ui 33.10.1 → 33.12.1
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/DESIGN.md +1 -0
- package/README.md +3 -3
- package/agent/DESIGN.md +1 -0
- package/agent/START-HERE.md +7 -7
- package/agent/components/Anchor.json +1 -1
- package/agent/components/AppShell.json +1 -1
- package/agent/components/Cascader.json +1 -0
- package/agent/components/DataTable.json +1 -1
- package/agent/components/DatePicker.json +1 -0
- package/agent/components/Input.json +1 -0
- package/agent/components/NumberInput.json +1 -0
- package/agent/components/Pagination.json +1 -1
- package/agent/components/PasswordInput.json +1 -0
- package/agent/components/ResizablePanel.json +1 -1
- package/agent/components/ScrollArea.json +1 -1
- package/agent/components/SearchInput.json +1 -0
- package/agent/components/Select.json +1 -0
- package/agent/components/Table.json +1 -1
- package/agent/components/TimePicker.json +1 -0
- package/agent/components/Topbar.json +8 -2
- package/agent/components/TopbarItem.json +7 -1
- package/agent/components/TreeSelect.json +1 -0
- package/agent/components-index.json +7 -7
- package/agent/components.json +31 -10
- package/agent/index.json +6 -6
- package/agent/llms.txt +6 -6
- package/agent/tokens.json +39 -3
- package/dist/components/data-display/data-table.js +2 -0
- package/dist/components/data-display/table.js +3 -0
- package/dist/components/data-entry/input.js +16 -1
- package/dist/components/data-entry/search-input.js +1 -1
- package/dist/components/layout/app-shell.js +16 -6
- package/dist/components/layout/topbar-item.d.ts +1 -0
- package/dist/components/layout/topbar-item.js +9 -4
- package/dist/components/layout/topbar.d.ts +1 -1
- package/dist/components/layout/topbar.js +32 -3
- package/dist/components/navigation/pagination.js +4 -0
- package/dist/contracts/measurement.json +1 -1
- package/dist/lab/anchor.js +11 -1
- package/dist/lib/focus-reveal.d.ts +5 -0
- package/dist/lib/focus-reveal.js +19 -0
- package/dist/lib/hooks.js +5 -2
- package/dist/props/components/layout.prop.d.ts +13 -0
- package/dist/props/registry.d.ts +9 -1
- package/dist/props/registry.js +10 -0
- package/dist/styles/control.css +131 -3
- package/dist/styles/focus-ring.css +79 -19
- package/dist/styles/layers.json +1 -1
- package/dist/styles/layout.css +4 -4
- package/dist/styles/shell-layout.css +48 -0
- package/dist/styles.css +1 -1
- package/dist/tokens/base.css +2 -0
- package/dist/tokens/components/control.css +4 -1
- package/dist/tokens/components/resizable.css +4 -0
- package/dist/tokens/components/shell.css +3 -0
- package/dist/tokens/foundation.css +4 -1
- package/docs/TOKENS.md +52 -23
- package/package.json +2 -2
- package/scripts/visual-audit.mjs +33 -3
package/agent/components.json
CHANGED
|
@@ -1323,7 +1323,7 @@
|
|
|
1323
1323
|
23
|
|
1324
1324
|
],
|
|
1325
1325
|
"storyPath": "layout/app-shell.tsx",
|
|
1326
|
-
"tagline": "Root application shell — composes sidebar, topbar rail, main content area, and optional footer.",
|
|
1326
|
+
"tagline": "Root application shell — composes sidebar, topbar rail, main content area, and optional footer. Keyboard focus in clipped chrome stays inset; the main scroll region shows an inset brand ring by default (--region-focus-ring-width).",
|
|
1327
1327
|
"usage": [
|
|
1328
1328
|
"DO put a sidebar collapse toggle IN the rail, not floating in the bar (ADR C10): the shell does not render one, so pass it through `Sidebar action={(collapsed) => <Button variant=\"ghost\" size=\"icon\" aria-label={t(\"toggleSidebar\")} aria-expanded={!collapsed} …/>}` (or the `footer` render-prop), which keeps it attached to the rail in both states. A toggle in `Topbar start` under `topbarSpan=\"full\"` lands beside the brand, detached from the rail it controls. The shell has no edge-mounted toggle (a control straddling the rail border); `Sidebar action` keeps it inside the rail head, which is the supported placement. Below the navigation boundary the drawer has its own trigger; render no collapse toggle there.",
|
|
1329
1329
|
"useAppShellNavigationMode() returns drawer/docked at the same 56.25rem boundary as AppShell. The root also publishes data-navigation. Use this instead of a consumer media query.",
|
|
@@ -1689,6 +1689,11 @@
|
|
|
1689
1689
|
"importPath": "@godxjp/ui/layout",
|
|
1690
1690
|
"name": "Topbar",
|
|
1691
1691
|
"props": [
|
|
1692
|
+
{
|
|
1693
|
+
"description": "Opt-in pinned shared-device identity: one TopbarItem truncate with Avatar in icon and a string name. Capped at --topbar-identity-max-inline-size (40%); --topbar-identity-min-text-inline-size reserves 3ch. Stays outside scroll/menu while primary cells retain their minima. Only other clusters follow overflow; children overrides all slots. Do not combine identity with hideBelow, labelHideBelow or iconHideFrom. At extreme widths choose overflow=\"menu\" to keep other controls reachable.",
|
|
1694
|
+
"name": "identity",
|
|
1695
|
+
"type": "ReactNode"
|
|
1696
|
+
},
|
|
1692
1697
|
{
|
|
1693
1698
|
"description": "Instance inset using logical token steps.",
|
|
1694
1699
|
"name": "pad",
|
|
@@ -1721,11 +1726,11 @@
|
|
|
1721
1726
|
"defaultValue": "\"scroll\"",
|
|
1722
1727
|
"description": "What a bar that does not fit does with the cells that do not fit (gh#728). scroll (default) lets the BAR scroll on the inline axis while the start and end clusters keep their cells whole — every cell stays in the DOM and in the accessibility tree, Tab reaches each one in order, and the browser scrolls a focused cell into view; the scrollbar is suppressed (a horizontal bar inside a 48px chrome row would eat the height that caused the deficit), so the pointer affordance is drag/wheel. clip is the pre-gh#728 behaviour: a cell past the cluster's edge is simply not painted — still focusable, still announced, invisible and unreachable by pointer. Independent of TopbarItem hideBelow, which REMOVES a cell at a step; this is only about what happens once there is no budget left.",
|
|
1723
1728
|
"name": "overflow",
|
|
1724
|
-
"type": "\"scroll\" | \"clip\""
|
|
1729
|
+
"type": "\"scroll\" | \"clip\" | \"menu\""
|
|
1725
1730
|
},
|
|
1726
1731
|
{
|
|
1727
1732
|
"defaultValue": "undefined",
|
|
1728
|
-
"description": "Escape hatch — render fully custom bar content instead of the three slots. When set, `start`/`center`/`end` are ignored.",
|
|
1733
|
+
"description": "Escape hatch — render fully custom bar content instead of the three slots. When set, `start`/`center`/`end`/`identity` are ignored.",
|
|
1729
1734
|
"name": "children",
|
|
1730
1735
|
"type": "ReactNode"
|
|
1731
1736
|
},
|
|
@@ -1750,6 +1755,7 @@
|
|
|
1750
1755
|
"storyPath": "layout/topbar.tsx",
|
|
1751
1756
|
"tagline": "A PURE SLOT bar for the app shell — positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",
|
|
1752
1757
|
"usage": [
|
|
1758
|
+
"For shared devices use identity={<TopbarItem truncate icon={<Avatar aria-hidden=\"true\"><AvatarFallback>DB</AvatarFallback></Avatar>}>{user.name}</TopbarItem>}, optionally wrapped in DropdownMenuTrigger asChild. Keep the locale picker icon-only with appearance=\"bar\" in end. The name stays visible and bounded; full string children become title and accessible text. AccountChip is a separate avatar/name/action row, not a full-height menu cell.",
|
|
1753
1759
|
"DO use `TopbarSearch`, not a bare `SearchInput`, for the bar's search (ADR C02): a SearchInput in a slot shrinks with the cluster — measured at 390px in a consumer bar its text area was 0ch. TopbarSearch keeps at least 8ch from 40rem up and folds to a named search cell below it.",
|
|
1754
1760
|
"DO align the bar's search with the content column (ADR C11) by composition: keep AppShell's default `topbarSpan=\"content\"` and put `TopbarSearch` FIRST in `start` — the bar then starts on the rail edge with the same inset as PageContainer, measured 0px apart at 1024/1280/1440. Under `topbarSpan=\"full\"` the bar starts at the window edge after the brand, so no keyline is shared; there, put the search in `end` deliberately rather than near a column it cannot meet.",
|
|
1755
1761
|
"DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",
|
|
@@ -1775,6 +1781,12 @@
|
|
|
1775
1781
|
"importPath": "@godxjp/ui/layout",
|
|
1776
1782
|
"name": "TopbarItem",
|
|
1777
1783
|
"props": [
|
|
1784
|
+
{
|
|
1785
|
+
"defaultValue": "false",
|
|
1786
|
+
"description": "Same vocabulary as Text.truncate: allow the cell to shrink to its control minimum and ellipse only its label. Keeps the icon; overrides labelHideBelow. Full text remains accessible; string children supply title automatically (including asChild). Pass title explicitly for rich children. Use in Topbar identity for a bounded always-visible shared-device name; default phone accounts still use labelHideBelow=\"sm\".",
|
|
1787
|
+
"name": "truncate",
|
|
1788
|
+
"type": "boolean"
|
|
1789
|
+
},
|
|
1778
1790
|
{
|
|
1779
1791
|
"description": "The cell's leading glyph, placed in a slot the CELL sizes (--topbar-icon-size). Prefer it over passing the glyph as a child whenever the glyph is wrapped: `.ui-topbar-item > svg` only reaches a DIRECT child, so a glyph inside a `<Flex hideBelow>` renders at lucide's intrinsic 24px — measured at 390px on an org-switcher cell, 1.5x the cell's step (gh#712). Decorative by convention (the cell's name comes from its own aria-label); pass <Icon as={…} size=… label=… /> here when the glyph needs a name or a different step. Rendered with asChild too (gh#726): the sized slot is inserted INSIDE the child element, ahead of that element's own children, and the child element (e.g. an <a>) stays the rendered root with every merged prop.",
|
|
1780
1792
|
"name": "icon",
|
|
@@ -1836,7 +1848,7 @@
|
|
|
1836
1848
|
"storyPath": "layout/topbar-item.tsx",
|
|
1837
1849
|
"tagline": "ONE interactive cell of a Topbar slot — the account button, a settings or notifications trigger. Full bar height, the bar's own hover surface, and the focus mark hosted INSIDE the cell. Use it INSTEAD OF a Button in a Topbar slot: a Button there is a --control-height pill floating in a taller bar, with its own hover fill and a ring drawn around the pill.",
|
|
1838
1850
|
"usage": [
|
|
1839
|
-
"
|
|
1851
|
+
"By default collapse the account cell to its avatar on a phone (ADR C04); shared devices opt into Topbar identity + TopbarItem truncate instead: `<DropdownMenuTrigger asChild><TopbarItem aria-label={t(\"account\")} icon={<Avatar>…</Avatar>} labelHideBelow=\"sm\">{name}</TopbarItem></DropdownMenuTrigger>` and repeat the name (and email) in the menu's `DropdownMenuLabel`. Measured at 390px: without `labelHideBelow` the name took 74px of a 390px bar; with it the cell is 48x47, named, and the name is read in the open menu.",
|
|
1840
1852
|
"DO put bar triggers in a Topbar slot as TopbarItem, not as Button — that is the difference between chrome and a control that landed in the chrome.",
|
|
1841
1853
|
"DO wrap it in a DropdownMenuTrigger asChild for a user menu; the open state lights the cell via [data-state=open].",
|
|
1842
1854
|
"DON'T set a height: the cell stretches to whatever the bar is (AppShell's grid row, --topbar-height, or the coarse-pointer bar), which is why there is no height knob.",
|
|
@@ -3613,7 +3625,7 @@
|
|
|
3613
3625
|
37
|
|
3614
3626
|
],
|
|
3615
3627
|
"storyPath": "data-display/data-table/index.tsx",
|
|
3616
|
-
"tagline": "The one TanStack-powered compound admin list — sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack).",
|
|
3628
|
+
"tagline": "The one TanStack-powered compound admin list — sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack). Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
|
|
3617
3629
|
"usage": [
|
|
3618
3630
|
"DO use `striped` on dense list tables (many columns, a row the eye must follow across the width). To stripe EVERY Table and DataTable in a service, set it ONCE in the theme — `:root { --table-row-striped-alpha: 100%; }` — instead of passing `striped` at each call site; `striped={false}` then opts one table out. Retint with `--table-row-striped-background`, never with a `rowClassName` utility or `:nth-child` page CSS (those count DOM rows, so an expanded detail row shifts every stripe after it).",
|
|
3619
3631
|
"DO pass loading={isFetching} during data fetches — it renders a loading row in the table body and suppresses the empty state. Never show a spinner outside DataTable while the table is visible.",
|
|
@@ -5634,7 +5646,7 @@
|
|
|
5634
5646
|
"TableHeader",
|
|
5635
5647
|
"TableRow"
|
|
5636
5648
|
],
|
|
5637
|
-
"tagline": "Primitive table shell (Table/TableHeader/TableBody/TableRow/TableHead/TableCell). Prefer DataTable for admin lists; use these for custom one-off tables.",
|
|
5649
|
+
"tagline": "Primitive table shell (Table/TableHeader/TableBody/TableRow/TableHead/TableCell). Prefer DataTable for admin lists; use these for custom one-off tables. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
|
|
5638
5650
|
"usage": [
|
|
5639
5651
|
"DO compose all six sub-parts in order: wrap with `<Table>`, then `<TableHeader>` containing `<TableRow><TableHead>…</TableRow>`, then `<TableBody>` containing one or more `<TableRow><TableCell>…` rows. Skipping any layer (e.g. bare `<th>` inside `<Table>`) bypasses the design tokens and hover/border styles.",
|
|
5640
5652
|
"DO use `TableHead` (not `TableCell`) for header cells — it renders `<th>` with `data-slot=\"table-head\"` and the `--table-row-height` CSS variable for consistent header sizing across the design system. `TableCell` renders `<td>` with `data-slot=\"table-cell\"` and is for body rows only.",
|
|
@@ -6221,6 +6233,7 @@
|
|
|
6221
6233
|
"storyPath": "data-entry/input.tsx",
|
|
6222
6234
|
"tagline": "Styled wrapper around native <input>; accepts all HTML input attributes. Pair with FormField for labelled fields.",
|
|
6223
6235
|
"usage": [
|
|
6236
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged. The trailing slot is measured, so adjacent actions and counters reserve their combined width; allowClear still replaces trailingIcon/suffix.",
|
|
6224
6237
|
"DO always wrap Input in FormField when the field needs a label, helper text, or validation error — FormField injects aria-describedby and aria-invalid onto Input automatically; never wire these attributes by hand.",
|
|
6225
6238
|
"DO match the `id` prop on Input to the `id` prop on its parent FormField so that `htmlFor` linkage and the generated helper/error ids are consistent.",
|
|
6226
6239
|
"DO use Input in controlled mode (`value` + `onChange`) for forms driven by Inertia's `useForm` or React state; uncontrolled usage (no `value`) is only acceptable for fire-and-forget inline edits where form state is not needed.",
|
|
@@ -6378,6 +6391,7 @@
|
|
|
6378
6391
|
"storyPath": "data-entry/number-input.tsx",
|
|
6379
6392
|
"tagline": "WAI-ARIA spinbutton for localized numeric entry — composes the real Input (role=spinbutton, inputMode=decimal) with stacked increment/decrement step Buttons. Type freely, Arrow/Shift-Arrow step, value commits clamped to min/max + rounded to precision.",
|
|
6380
6393
|
"usage": [
|
|
6394
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged. The increment/decrement actions sit side by side on touch screens so each owns a non-overlapping target.",
|
|
6381
6395
|
"DO use NumberInput (not `<Input type='number'>`) whenever numeric entry wants steppers, min/max clamping, precision rounding, or a ¥/% affix — it is the canonical numeric primitive. Plain Input has no stepper and no clamp.",
|
|
6382
6396
|
"DO drive it controlled with `value` + `onValueChange` carrying `number | null` (the vocabulary triad — NOT `onChange`). `null` means the field is empty; never substitute 0 for empty.",
|
|
6383
6397
|
"DON'T pass `value` without `onValueChange` — like every controlled @godxjp/ui input it would freeze. Omit both for uncontrolled (use `defaultValue`).",
|
|
@@ -6488,6 +6502,7 @@
|
|
|
6488
6502
|
"storyPath": "data-entry/search-input.tsx",
|
|
6489
6503
|
"tagline": "Debounced search box with a clear button. Fires onSearch (NOT onChange) after the debounce. Controlled (value) or uncontrolled (defaultValue).",
|
|
6490
6504
|
"usage": [
|
|
6505
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
6491
6506
|
"DON'T drop a bare SearchInput into a Topbar slot — use `TopbarSearch` (layout), which passes every SearchInput prop through and folds to a search cell below 40rem instead of shrinking under 8ch (ADR C02).",
|
|
6492
6507
|
"DO: listen to `onSearch`, not `onChange`. The component debounces internally (default 250 ms) and fires `onSearch(q)` after the delay — never wire your filter logic to `onChange` on SearchInput because it does not expose one.",
|
|
6493
6508
|
"DO: choose controlled vs uncontrolled deliberately. Pass `value` + `onValueChange` together for controlled mode; optionally add `onSearch` for debounced effects (e.g. when search state lives in a URL param or shared parent). For local-only ephemeral search pass only `defaultValue` + `onSearch` — omitting `value` puts the component in uncontrolled mode.",
|
|
@@ -7028,6 +7043,7 @@
|
|
|
7028
7043
|
],
|
|
7029
7044
|
"tagline": "Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually — never use a raw <select>.",
|
|
7030
7045
|
"usage": [
|
|
7046
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
7031
7047
|
"FREE-TEXT TAGS WITH NO LIST (keywords, email recipients, labels typed from scratch): `<Select mode=\"tags\" open={false} value={tags} onValueChange={setTags} />` — the inline chip field. Use plain `mode=\"tags\"` (popup) only when there is a list of existing tags to pick from. There is no `TagInput` component.",
|
|
7032
7048
|
"DO use the data-driven API (options/loadOptions) for straightforward selects — it handles grouping, search, async, and custom rendering automatically. Only reach for the compound API when you need to inject arbitrary content into the trigger or listbox.",
|
|
7033
7049
|
"DO pass name= on the data-driven Select so the value is submitted with a native form or Inertia useForm. Without name= the value is React-only and will not appear in form data.",
|
|
@@ -7995,6 +8011,7 @@
|
|
|
7995
8011
|
"storyPath": "data-entry/date-picker.tsx",
|
|
7996
8012
|
"tagline": "ONE date control: `picker` sets the granularity (day · week · month · quarter · year), `range` makes it a two-endpoint field, `multiple` a set. A real typeable input holds the value and submits ISO-8601 at the picker's own precision; the panel is the visual-only affordance.",
|
|
7997
8013
|
"usage": [
|
|
8014
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
7998
8015
|
"ONE component covers every date-shaped field. `picker` is the granularity axis, `range` and `multiple` are the cardinality axis, and they compose: `<DatePicker range picker=\"month\" />` is a month range.",
|
|
7999
8016
|
"DO use `name` to make the field form-submittable — it emits ISO-8601 at the picker's own precision (`2026-03` for a month, `2026` for a year, `2026-03-01` for a day). No hidden input of your own is needed.",
|
|
8000
8017
|
"DO wrap it in FormField like every other labelled control; FormField injects the id/aria wiring onto the input.",
|
|
@@ -8730,7 +8747,7 @@
|
|
|
8730
8747
|
"PaginationNext",
|
|
8731
8748
|
"PaginationPrevious"
|
|
8732
8749
|
],
|
|
8733
|
-
"tagline": "Offset/page-based pagination bar. Sits below a table card.",
|
|
8750
|
+
"tagline": "Offset/page-based pagination bar. Sits below a table card. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
|
|
8734
8751
|
"usage": [
|
|
8735
8752
|
"DO always control Pagination externally: store `value` (page) and `pageSize` in React state (or URL params), and update both in the `onValueChange(page, pageSize)` callback. Pagination is fully controlled — it has no internal state and will not move unless `value` changes.",
|
|
8736
8753
|
"DO let Pagination hide itself for zero items and single pages (`hideOnSinglePage`, default true) — it is navigation between multiple result pages. Render it inside a table footer only in the DATA state: never during loading, empty, error, or an unmet prerequisite. Pass `hideOnSinglePage={false}` only when you still want the bar on one page to keep `showTotal` visible.",
|
|
@@ -9653,6 +9670,7 @@
|
|
|
9653
9670
|
"storyPath": "data-entry/time-picker.tsx",
|
|
9654
9671
|
"tagline": "Time combobox with hours, minutes, optional seconds, confirmation and 12-hour formatting. Native submission stays canonical 24-hour time.",
|
|
9655
9672
|
"usage": [
|
|
9673
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
9656
9674
|
"One trailing icon: empty shows calendar/clock; filled and clearable shows only ×; allowClear=false retains the picker icon. Click the input or ArrowDown to open a filled picker.",
|
|
9657
9675
|
"DO give it a name in native forms. It submits HH:mm or HH:mm:ss, regardless of display format; the component manages any hidden canonical field.",
|
|
9658
9676
|
"DO use the controlled pattern (`value` + `onValueChange`) in React-managed forms (e.g. useForm). For simple HTML forms without React state, omit `value` and use `defaultValue` for the uncontrolled pattern.",
|
|
@@ -9868,6 +9886,7 @@
|
|
|
9868
9886
|
"storyPath": "data-entry/cascader.tsx",
|
|
9869
9887
|
"tagline": "Multi-level hierarchical path picker (Popover + cascading columns); value is always a string[] path, never a flat ID — passing a bare string breaks it.",
|
|
9870
9888
|
"usage": [
|
|
9889
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
9871
9890
|
"DO pass a string[] path as value in single mode (e.g. ['country','region','city']). DON'T pass a flat string ID — the component treats value as an ordered path array and will render nothing if you pass a bare string.",
|
|
9872
9891
|
"DO use value + onChange together for controlled mode, or defaultValue alone for uncontrolled. DON'T mix both — providing value without onChange makes the field read-only (the internal state won't update).",
|
|
9873
9892
|
"DO set multiple={true} and pass value as string[][] (array of paths) for multi-selection. onChange receives string[][] in that mode. Mixing single-mode shape with multiple={true} silently produces no selection.",
|
|
@@ -10100,6 +10119,7 @@
|
|
|
10100
10119
|
"storyPath": "data-entry/tree-select.tsx",
|
|
10101
10120
|
"tagline": "Hierarchical tree picker in a Popover (single or multi-select with checkboxes) — `onValueChange` receives `string` in single mode and `string[]` in multi/checkable mode; never use a raw `<select>` for tree-structured data.",
|
|
10102
10121
|
"usage": [
|
|
10122
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
10103
10123
|
"DO pair with a `<label htmlFor={id}>` and pass the matching `id` prop so screen readers announce the control correctly. The underlying trigger is a `<Button role='combobox'>` — not a native `<select>` — so an explicit label is required.",
|
|
10104
10124
|
"DO use `treeCheckable` (+ optionally `showCheckedStrategy`) for selecting multiple nodes with parent–child cascade; use `multiple` only when you want multi-select WITHOUT the checkbox cascade behaviour.",
|
|
10105
10125
|
"DO use the static constants `TreeSelect.SHOW_CHILD`, `TreeSelect.SHOW_PARENT`, `TreeSelect.SHOW_ALL` (or the named exports `SHOW_CHILD`/`SHOW_PARENT`/`SHOW_ALL` from the same import path) instead of raw string literals for `showCheckedStrategy`.",
|
|
@@ -11654,7 +11674,7 @@
|
|
|
11654
11674
|
"subParts": [
|
|
11655
11675
|
"ScrollBar"
|
|
11656
11676
|
],
|
|
11657
|
-
"tagline": "A native scrolling box (no scroll library since v23): one `overflow: auto` element whose scrollbar is styled from --scroll-area-* tokens. Always set an explicit height/max-height, or nothing overflows and no scrollbar appears. Owns the scrolling element, so it also owns reaching it (viewportRef) and bottom anchoring for a live stream (anchor).",
|
|
11677
|
+
"tagline": "Keyboard focus on the scroll viewport and its descendants uses the shared inset brand ring (--focus-ring-inset-offset), without padding changes. A native scrolling box (no scroll library since v23): one `overflow: auto` element whose scrollbar is styled from --scroll-area-* tokens. Always set an explicit height/max-height, or nothing overflows and no scrollbar appears. Owns the scrolling element, so it also owns reaching it (viewportRef) and bottom anchoring for a live stream (anchor).",
|
|
11658
11678
|
"usage": [
|
|
11659
11679
|
"DO set an explicit height or max-height on ScrollArea via className (e.g. `className=\"h-64\"` or `className=\"max-h-[min(300px,50vh)]\"`). Without a height constraint the viewport grows to fit content and the scrollbar is never rendered.",
|
|
11660
11680
|
"DO wrap content in a single child element inside ScrollArea — the Viewport observes its single child's size to decide whether overflow exists.",
|
|
@@ -12756,6 +12776,7 @@
|
|
|
12756
12776
|
"storyPath": "data-entry/password-input.tsx",
|
|
12757
12777
|
"tagline": "Input for passwords with a built-in show/hide eye toggle. Accepts all Input props except `type`.",
|
|
12758
12778
|
"usage": [
|
|
12779
|
+
"Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
|
|
12759
12780
|
"DO use for any password / secret field instead of `<Input type=\"password\">` so users get the show/hide affordance.",
|
|
12760
12781
|
"DO pass `name` + `autoComplete=\"current-password\"|\"new-password\"` for correct form/password-manager behavior.",
|
|
12761
12782
|
"DON'T add your own eye button — it's built in (and excluded from the tab order)."
|
|
@@ -13206,7 +13227,7 @@
|
|
|
13206
13227
|
"ResizableHandle",
|
|
13207
13228
|
"ResizablePanelGroup"
|
|
13208
13229
|
],
|
|
13209
|
-
"tagline": "Resizable panel group/child/handle primitives from react-resizable-panels.",
|
|
13230
|
+
"tagline": "Resizable panel group/child/handle primitives from react-resizable-panels. --resizable-handle-size sets separator thickness; ARIA orientation controls its axis. Handles use the shared inset focus ring, including empty handles.",
|
|
13210
13231
|
"usage": [
|
|
13211
13232
|
"DO put the layout on `<ResizablePanelGroup orientation=\"horizontal|vertical\">`, the resizable regions in `<ResizablePanel>`, and a `<ResizableHandle>` BETWEEN every adjacent pair — a group of N panels needs N-1 handles or there is nothing to drag.",
|
|
13212
13233
|
"DO express `defaultSize`/`minSize`/`maxSize` as PERCENTAGE STRINGS like `defaultSize=\"35%\"` (react-resizable-panels v4: a bare number is PIXELS, so `defaultSize={35}` renders a 35px sliver, NOT 35% — pass the string). Don't fight sizing with a fixed `w-[280px]` className on the panel.",
|
|
@@ -16666,7 +16687,7 @@
|
|
|
16666
16687
|
45
|
|
16667
16688
|
],
|
|
16668
16689
|
"storyPath": "navigation/anchor.tsx",
|
|
16669
|
-
"tagline": "Ant Design `Anchor` (6.6.5): the in-page section navigation, and the only thing in this library that COMPUTES which section is current. `NavList activeId` takes that answer as a prop. Resolves by antd's rule — the last section whose edge has crossed a single decision line — which is a pure function of scroll position and therefore cannot flicker, suppresses itself during the programmatic scroll a click starts, reads the landing hash before any scroll event, and jumps rather than tweens under `prefers-reduced-motion`.",
|
|
16690
|
+
"tagline": "Ant Design `Anchor` (6.6.5): the in-page section navigation, and the only thing in this library that COMPUTES which section is current. `NavList activeId` takes that answer as a prop. Resolves by antd's rule — the last section whose edge has crossed a single decision line — which is a pure function of scroll position and therefore cannot flicker, suppresses itself during the programmatic scroll a click starts, reads the landing hash before any scroll event, and jumps rather than tweens under `prefers-reduced-motion`. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
|
|
16670
16691
|
"tier": "lab",
|
|
16671
16692
|
"usage": [
|
|
16672
16693
|
"DO give every target a real `id` and point `href` at it as `#id`. The links are real anchors: middle-clickable, deep-linkable, and correct before JavaScript boots.",
|
package/agent/index.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"components": 169,
|
|
5
5
|
"patterns": 28,
|
|
6
6
|
"rules": 44,
|
|
7
|
-
"tokens":
|
|
7
|
+
"tokens": 2241,
|
|
8
8
|
"vocabulary": 14
|
|
9
9
|
},
|
|
10
10
|
"design": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"file": "components-index.json",
|
|
19
|
-
"note": "
|
|
19
|
+
"note": "53 KB — name + group + tagline for all 169. FETCH THIS FIRST, then fetch only the components you chose.",
|
|
20
20
|
"url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
@@ -54,19 +54,19 @@
|
|
|
54
54
|
"note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
|
|
55
55
|
"read": {
|
|
56
56
|
"live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
|
|
57
|
-
"pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.
|
|
57
|
+
"pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.12.1/agent/index.json"
|
|
58
58
|
},
|
|
59
59
|
"source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
|
|
60
60
|
"start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
|
|
61
61
|
"tokenTiers": {
|
|
62
62
|
"component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
|
|
63
63
|
"counts": {
|
|
64
|
-
"component":
|
|
65
|
-
"foundation":
|
|
64
|
+
"component": 1910,
|
|
65
|
+
"foundation": 218,
|
|
66
66
|
"semantic": 113
|
|
67
67
|
},
|
|
68
68
|
"foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
|
|
69
69
|
"semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
|
|
70
70
|
},
|
|
71
|
-
"version": "33.
|
|
71
|
+
"version": "33.12.1"
|
|
72
72
|
}
|
package/agent/llms.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @godxjp/ui
|
|
2
2
|
|
|
3
|
-
> A Japanese-enterprise React design system: 169 components,
|
|
4
|
-
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.
|
|
3
|
+
> A Japanese-enterprise React design system: 169 components, 2241 design tokens,
|
|
4
|
+
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.12.1.
|
|
5
5
|
|
|
6
6
|
If your client can run a process, do not read these files — run the MCP server instead
|
|
7
|
-
(`npx @godxjp/ui-mcp@33.
|
|
7
|
+
(`npx @godxjp/ui-mcp@33.12.1`). It is searchable and version-locked. These files exist for agents
|
|
8
8
|
that can only fetch URLs.
|
|
9
9
|
|
|
10
10
|
## Start
|
|
@@ -16,10 +16,10 @@ that can only fetch URLs.
|
|
|
16
16
|
## Catalog
|
|
17
17
|
|
|
18
18
|
- [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 28 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
|
|
19
|
-
- [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json):
|
|
19
|
+
- [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 53 KB — all 169 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
|
|
20
20
|
- [components/<Name>.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–37 KB, median 6 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
|
|
21
21
|
- [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.3 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
|
|
22
|
-
- [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` —
|
|
22
|
+
- [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 218 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 1910 `component` knobs.
|
|
23
23
|
- [vocabulary.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json): the controlled prop vocabulary — which prop name means what, across every component.
|
|
24
24
|
- [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 44 cardinal rules.
|
|
25
25
|
- [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 35 shapes that make generated UI look generated, each with its fix.
|
|
@@ -27,7 +27,7 @@ that can only fetch URLs.
|
|
|
27
27
|
## Pinning
|
|
28
28
|
|
|
29
29
|
Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
|
|
30
|
-
the tag: `.../godx-jp/godxjp-ui/v33.
|
|
30
|
+
the tag: `.../godx-jp/godxjp-ui/v33.12.1/agent/...`. A catalog that does not match the installed
|
|
31
31
|
package describes props that are absent, or hides props that are present, and says nothing either way.
|
|
32
32
|
|
|
33
33
|
Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
|
package/agent/tokens.json
CHANGED
|
@@ -665,6 +665,18 @@
|
|
|
665
665
|
"tier": "foundation",
|
|
666
666
|
"value": "var(--focus-outline-offset)"
|
|
667
667
|
},
|
|
668
|
+
{
|
|
669
|
+
"description": "Inside clipping surfaces, preserve the mark and gap without changing layout.",
|
|
670
|
+
"name": "--focus-ring-inset-offset",
|
|
671
|
+
"tier": "foundation",
|
|
672
|
+
"value": "calc(-1 * (var(--focus-ring-width) + var(--focus-outline-offset)))"
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
"description": "Foundation tokens: color, typography, raw spacing, ratio, radius, shadow.",
|
|
676
|
+
"name": "--focus-ring-contained-offset",
|
|
677
|
+
"tier": "foundation",
|
|
678
|
+
"value": "initial"
|
|
679
|
+
},
|
|
668
680
|
{
|
|
669
681
|
"description": "Ring headroom for clipped chrome (`overflow-clip-margin` on the topbar family, gh#291). MUST stay a plain length consumed via a bare var(): Chromium rejects any calc() in overflow-clip-margin at parse time (even calc(2 * 2px)), silently dropping the declaration to 0. Value = max ring paint (3px open/focus) + rounding immunity: at 4px a flush-edge ring had only 1px of slack, which device-pixel rounding on non-retina (dpr 1) displays or fractional browser zoom could consume, shaving the ring's outer edge. 8px keeps ≥5px of slack under any rounding regime.",
|
|
670
682
|
"name": "--focus-ring-clip-margin",
|
|
@@ -672,10 +684,10 @@
|
|
|
672
684
|
"value": "8px"
|
|
673
685
|
},
|
|
674
686
|
{
|
|
675
|
-
"description": "
|
|
687
|
+
"description": "Keyboard-scroll regions use an inset brand mark by default. Both the region weight and the global focus switch remain configurable.",
|
|
676
688
|
"name": "--region-focus-ring-width",
|
|
677
689
|
"tier": "foundation",
|
|
678
|
-
"value": "
|
|
690
|
+
"value": "var(--focus-ring-weight)"
|
|
679
691
|
},
|
|
680
692
|
{
|
|
681
693
|
"description": "default = hsl(var(--focus-ring-color, var(--ring)))",
|
|
@@ -4067,6 +4079,12 @@
|
|
|
4067
4079
|
"tier": "component",
|
|
4068
4080
|
"value": "1.5rem"
|
|
4069
4081
|
},
|
|
4082
|
+
{
|
|
4083
|
+
"description": "Shared coarse-pointer floor; independent of density and the desktop glyph size.",
|
|
4084
|
+
"name": "--control-affix-touch-target-size",
|
|
4085
|
+
"tier": "component",
|
|
4086
|
+
"value": "var(--band-height-xl)"
|
|
4087
|
+
},
|
|
4070
4088
|
{
|
|
4071
4089
|
"description": "gh#888 — φ tier freezes at :root; default = calc(var(--radius) / var(--radius-ratio) / var(--radius-ratio))",
|
|
4072
4090
|
"name": "--control-affix-action-radius",
|
|
@@ -5139,7 +5157,7 @@
|
|
|
5139
5157
|
"description": "SearchInput's text stops at the clear ✕'s 44px TARGET, not at its 24px box (gh#1148): edge inset + half the box (where the glyph's centre is) + half the target.",
|
|
5140
5158
|
"name": "--search-input-end-padding",
|
|
5141
5159
|
"tier": "component",
|
|
5142
|
-
"value": "calc( var(--search-input-edge-inset) + var(--touch-target-min) / 2 + var(--
|
|
5160
|
+
"value": "calc( var(--search-input-edge-inset) + var(--touch-target-min) / 2 + var(--control-affix-touch-target-size) / 2 )"
|
|
5143
5161
|
},
|
|
5144
5162
|
{
|
|
5145
5163
|
"description": "Control primitive tokens: heights, horizontal padding, adjacent control sizes.",
|
|
@@ -10055,6 +10073,12 @@
|
|
|
10055
10073
|
"tier": "component",
|
|
10056
10074
|
"value": "var(--space-2)"
|
|
10057
10075
|
},
|
|
10076
|
+
{
|
|
10077
|
+
"description": "Room for the shared inset focus mark and gap, including handles without a grip.",
|
|
10078
|
+
"name": "--resizable-handle-size",
|
|
10079
|
+
"tier": "component",
|
|
10080
|
+
"value": "var(--space-2)"
|
|
10081
|
+
},
|
|
10058
10082
|
{
|
|
10059
10083
|
"description": "`thin` reproduces the old 10px rail closely on the platforms that draw a classic scrollbar; `auto` gives the full-width platform bar, which is what a touch/coarse-pointer console wants. Only `auto | thin | none` are valid — a length is not (that is the standard property's whole bargain, and the reason `--scroll-area-bar-size` could not survive as a rem).",
|
|
10060
10084
|
"name": "--scroll-area-scrollbar-width",
|
|
@@ -10895,6 +10919,18 @@
|
|
|
10895
10919
|
"tier": "component",
|
|
10896
10920
|
"value": "var(--space-2)"
|
|
10897
10921
|
},
|
|
10922
|
+
{
|
|
10923
|
+
"description": "Shared-device identity gets a bounded share, not the primary controls' width.",
|
|
10924
|
+
"name": "--topbar-identity-max-inline-size",
|
|
10925
|
+
"tier": "component",
|
|
10926
|
+
"value": "40%"
|
|
10927
|
+
},
|
|
10928
|
+
{
|
|
10929
|
+
"description": "Shell (sidebar / topbar / kbd) component tokens — small-by-design text knobs (rule #45/#46). A service re-tunes chrome text without moving the global scale.",
|
|
10930
|
+
"name": "--topbar-identity-min-text-inline-size",
|
|
10931
|
+
"tier": "component",
|
|
10932
|
+
"value": "3ch"
|
|
10933
|
+
},
|
|
10898
10934
|
{
|
|
10899
10935
|
"description": "THE GLASS BAR (gh#831) — the same two declarations two unrelated website showcases each hand-wrote on their own sticky header: a translucent page fill plus a backdrop blur. Both knobs are `initial`, i.e. QUIET by default in exactly the sense the paragraph above describes: a guaranteed-invalid value makes the whole declaration at the call site invalid-at-computed-value time, so `background-color` falls back to its own initial `transparent` and `backdrop-filter` to `none`. The shipped bar therefore paints nothing, creates no backdrop root, and is byte-identical to the pure slot bar it was. A service opts in with `--topbar-background-alpha: 85%` / `--topbar-backdrop-blur-size: 10px`. The TINT is NOT a knob: `.ui-topbar` reads `--background` directly at the call site, so a dark or tenant-scoped subtree re-tints the glass instead of inheriting :root's answer (docs/TOKENS.md · the `:root` freeze rule). Precedent for the blur name: --app-launcher-launchpad-backdrop-blur-size. default = none — the bar paints no fill",
|
|
10900
10936
|
"name": "--topbar-background-alpha",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { revealKeyboardFocus } from "../../lib/focus-reveal.js";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import {
|
|
5
6
|
columnFilteringFeature,
|
|
@@ -1000,6 +1001,7 @@ DataTable.Content = function DataTableContent() {
|
|
|
1000
1001
|
"div",
|
|
1001
1002
|
{
|
|
1002
1003
|
ref: scrollRef,
|
|
1004
|
+
onFocusCapture: revealKeyboardFocus,
|
|
1003
1005
|
className: cn(
|
|
1004
1006
|
"ui-data-table-scroll",
|
|
1005
1007
|
hasPinEnd && "ui-data-table-has-pin-end",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { revealKeyboardFocus } from "../../lib/focus-reveal.js";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
6
|
import { tableHeadHeightClass } from "../../lib/control-styles.js";
|
|
@@ -153,7 +154,9 @@ const Table = React.forwardRef(
|
|
|
153
154
|
"div",
|
|
154
155
|
{
|
|
155
156
|
ref: scrollRef,
|
|
157
|
+
onFocusCapture: revealKeyboardFocus,
|
|
156
158
|
className: cn(
|
|
159
|
+
"ui-table-scroll",
|
|
157
160
|
scrollable ? "relative w-full overflow-auto" : "relative w-full",
|
|
158
161
|
preset === "action-collection" && "ui-table-collection",
|
|
159
162
|
preset === "stacked-record-collection" && "ui-table-stacked-collection"
|
|
@@ -72,6 +72,21 @@ const Input = React.forwardRef(
|
|
|
72
72
|
},
|
|
73
73
|
[ref]
|
|
74
74
|
);
|
|
75
|
+
const setTrailingRef = React.useCallback((node) => {
|
|
76
|
+
if (!node) return;
|
|
77
|
+
const wrapper = node.parentElement;
|
|
78
|
+
const sync = () => wrapper.style.setProperty(
|
|
79
|
+
"--input-trailing-measured-size",
|
|
80
|
+
`${node.getBoundingClientRect().width}px`
|
|
81
|
+
);
|
|
82
|
+
sync();
|
|
83
|
+
const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(sync);
|
|
84
|
+
observer?.observe(node);
|
|
85
|
+
return () => {
|
|
86
|
+
observer?.disconnect();
|
|
87
|
+
wrapper.style.removeProperty("--input-trailing-measured-size");
|
|
88
|
+
};
|
|
89
|
+
}, []);
|
|
75
90
|
const [text, setText] = React.useState(() => String(value ?? defaultValue ?? ""));
|
|
76
91
|
const hasText = text.length > 0;
|
|
77
92
|
React.useEffect(() => {
|
|
@@ -167,7 +182,7 @@ const Input = React.forwardRef(
|
|
|
167
182
|
...appearance
|
|
168
183
|
}
|
|
169
184
|
),
|
|
170
|
-
trailing != null || counter !== null ? /* @__PURE__ */ jsxs("span", { className: "ui-input-trailing", children: [
|
|
185
|
+
trailing != null || counter !== null ? /* @__PURE__ */ jsxs("span", { className: "ui-input-trailing", ref: setTrailingRef, children: [
|
|
171
186
|
counter ? /* @__PURE__ */ jsx(
|
|
172
187
|
"span",
|
|
173
188
|
{
|
|
@@ -104,7 +104,7 @@ function SearchInput({
|
|
|
104
104
|
// gh#1250 — while the key cap shows, the text stops before it instead of running under it.
|
|
105
105
|
// gh#1285 — written FROM --search-input-end-padding (minus the ✕ slot it reserves, plus the
|
|
106
106
|
// cap), so a theme that retunes that token still moves this padding (default: edge inset + 6ch, as before).
|
|
107
|
-
showShortcut ? "!pe-[calc(var(--search-input-end-padding)_-_var(--control-icon-size)_-_var(--control-gap)_+_6ch)]" :
|
|
107
|
+
showShortcut ? "!pe-[calc(var(--search-input-end-padding)_-_var(--control-icon-size)_-_var(--control-gap)_+_6ch)]" : void 0,
|
|
108
108
|
inputClassName
|
|
109
109
|
),
|
|
110
110
|
disabled
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { revealKeyboardFocus } from "../../lib/focus-reveal.js";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
import { Menu } from "lucide-react";
|
|
5
6
|
import { useMediaQuery } from "../../lib/hooks.js";
|
|
@@ -80,10 +81,10 @@ function AppShell({
|
|
|
80
81
|
/* @__PURE__ */ jsx("span", { className: "app-topbar-brand", "data-hide-below": logoCompactBelow, children: logo }),
|
|
81
82
|
/* @__PURE__ */ jsx("span", { className: "app-topbar-brand", "data-hide-from": logoCompactBelow, children: logoCompact })
|
|
82
83
|
] });
|
|
83
|
-
const resolvedTopbar = topbar !== void 0 ? /* @__PURE__ */ jsxs("div", { className: "app-topbar-rail", children: [
|
|
84
|
+
const resolvedTopbar = topbar !== void 0 ? /* @__PURE__ */ jsxs("div", { onFocusCapture: revealKeyboardFocus, className: "app-topbar-rail", children: [
|
|
84
85
|
!logoInRail && logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: brand }),
|
|
85
86
|
/* @__PURE__ */ jsx("div", { className: "app-topbar-custom", children: topbar })
|
|
86
|
-
] }) : /* @__PURE__ */ jsxs("div", { className: "app-topbar-rail", children: [
|
|
87
|
+
] }) : /* @__PURE__ */ jsxs("div", { onFocusCapture: revealKeyboardFocus, className: "app-topbar-rail", children: [
|
|
87
88
|
!logoInRail && logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: brand }),
|
|
88
89
|
topbarLeft !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-left", children: topbarLeft }),
|
|
89
90
|
/* @__PURE__ */ jsx("div", { className: "app-topbar-spacer" }),
|
|
@@ -167,10 +168,19 @@ function AppShell({
|
|
|
167
168
|
rail,
|
|
168
169
|
bar
|
|
169
170
|
] }),
|
|
170
|
-
/* @__PURE__ */ jsxs(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
/* @__PURE__ */ jsxs(
|
|
172
|
+
"main",
|
|
173
|
+
{
|
|
174
|
+
onFocusCapture: revealKeyboardFocus,
|
|
175
|
+
className: "app-main",
|
|
176
|
+
"aria-label": t("layout.appShell.mainLabel"),
|
|
177
|
+
tabIndex: 0,
|
|
178
|
+
children: [
|
|
179
|
+
breadcrumb !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-breadcrumb", children: breadcrumb }),
|
|
180
|
+
children
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
),
|
|
174
184
|
footer !== void 0 && /* @__PURE__ */ jsx("footer", { className: "app-footer", "aria-label": t("layout.appShell.footerLabel"), children: footer })
|
|
175
185
|
]
|
|
176
186
|
}
|
|
@@ -15,6 +15,7 @@ export declare const TopbarItem: React.ForwardRefExoticComponent<Omit<React.Butt
|
|
|
15
15
|
icon?: React.ReactNode;
|
|
16
16
|
iconHideFrom?: import("../../props/index.js").BreakpointProp;
|
|
17
17
|
labelHideBelow?: import("../../props/index.js").BreakpointProp;
|
|
18
|
+
truncate?: boolean;
|
|
18
19
|
badge?: React.ReactNode;
|
|
19
20
|
badgeTone?: import("../../props/components/layout.prop.js").SidebarBadgeToneProp;
|
|
20
21
|
asChild?: boolean;
|
|
@@ -10,6 +10,7 @@ const TopbarItem = React.forwardRef(function TopbarItem2({
|
|
|
10
10
|
icon,
|
|
11
11
|
iconHideFrom,
|
|
12
12
|
labelHideBelow,
|
|
13
|
+
truncate = false,
|
|
13
14
|
badge,
|
|
14
15
|
badgeTone,
|
|
15
16
|
children,
|
|
@@ -26,13 +27,14 @@ const TopbarItem = React.forwardRef(function TopbarItem2({
|
|
|
26
27
|
children: icon
|
|
27
28
|
}
|
|
28
29
|
) : null;
|
|
29
|
-
const labelOf = (
|
|
30
|
+
const labelOf = (label2) => !truncate && labelHideBelow === void 0 || label2 === void 0 || label2 === null || label2 === false ? label2 : /* @__PURE__ */ jsx(
|
|
30
31
|
"span",
|
|
31
32
|
{
|
|
33
|
+
dir: truncate ? "auto" : void 0,
|
|
32
34
|
"data-slot": "topbar-item-label",
|
|
33
35
|
className: "ui-topbar-item-label",
|
|
34
|
-
"data-hide-below": labelHideBelow,
|
|
35
|
-
children:
|
|
36
|
+
"data-hide-below": truncate ? void 0 : labelHideBelow,
|
|
37
|
+
children: label2
|
|
36
38
|
}
|
|
37
39
|
);
|
|
38
40
|
const badgeNode = showBadge ? /* @__PURE__ */ jsx(
|
|
@@ -44,18 +46,21 @@ const TopbarItem = React.forwardRef(function TopbarItem2({
|
|
|
44
46
|
children: badge
|
|
45
47
|
}
|
|
46
48
|
) : null;
|
|
47
|
-
const slotted = asChild && (iconNode !== null || labelHideBelow !== void 0 || badgeNode !== null) && React.isValidElement(children) ? React.cloneElement(
|
|
49
|
+
const slotted = asChild && (iconNode !== null || truncate || labelHideBelow !== void 0 || badgeNode !== null) && React.isValidElement(children) ? React.cloneElement(
|
|
48
50
|
children,
|
|
49
51
|
void 0,
|
|
50
52
|
iconNode,
|
|
51
53
|
labelOf(children.props.children),
|
|
52
54
|
badgeNode
|
|
53
55
|
) : children;
|
|
56
|
+
const label = asChild && React.isValidElement(children) ? children.props.children : children;
|
|
54
57
|
return /* @__PURE__ */ jsx(
|
|
55
58
|
Comp,
|
|
56
59
|
{
|
|
57
60
|
ref,
|
|
58
61
|
"data-slot": "topbar-item",
|
|
62
|
+
"data-truncate": truncate ? "true" : void 0,
|
|
63
|
+
title: truncate && typeof label === "string" ? label : void 0,
|
|
59
64
|
"data-hide-below": hideBelow,
|
|
60
65
|
className: cn("ui-topbar-item ui-focus-ring", className),
|
|
61
66
|
type: asChild ? void 0 : type ?? "button",
|
|
@@ -5,4 +5,4 @@ export type { TopbarProp, TopbarProp as TopbarProps } from "../../props/componen
|
|
|
5
5
|
* Topbar — a PURE SLOT bar. It positions three clusters (`start` / `center` / `end`) and owns the
|
|
6
6
|
* bar's flex layout; it does NOT bake any chrome.
|
|
7
7
|
*/
|
|
8
|
-
export declare function Topbar({ start, center, end, className, children, height, overflow, pad, style, ...props }: TopbarProp): React.JSX.Element;
|
|
8
|
+
export declare function Topbar({ start, center, end, identity, className, children, height, overflow, pad, style, onFocusCapture, ...props }: TopbarProp): React.JSX.Element;
|