@godxjp/ui 31.12.0 → 31.14.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.
@@ -3,7 +3,7 @@
3
3
  You are about to write code against a design system you did not author. This file is the whole
4
4
  contract. Read it before you write JSX.
5
5
 
6
- **This catalog describes `@godxjp/ui` 31.12.0.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 31.14.0.** If the project you are editing has a different
7
7
  version in its `package.json`, read the pinned catalog for THAT version instead
8
8
  (`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
9
9
  not exist yet; older, and it hides props that do. Neither failure announces itself.
@@ -56,10 +56,10 @@ Then ask it: `search_components`, `get_component`, `get_tokens`, `get_rule`, `li
56
56
  its `importPath`, and its examples. Fetch only the handful you picked in step 1.
57
57
  3. `rules.json` — 50 cardinal rules. The ones about raw HTML and hardcoded colour are not
58
58
  style advice.
59
- 4. `tokens.json` — 2116 design tokens, each tagged with its `tier`. **If you were handed a
59
+ 4. `tokens.json` — 2119 design tokens, each tagged with its `tier`. **If you were handed a
60
60
  brand, read the 211 `foundation` entries first** — `--primary`, `--background`,
61
61
  `--radius`, `--font-size-base` are the handful everything else derives from. The
62
- 1801 `component` entries are per-part knobs; reach for one only when a role is
62
+ 1804 `component` entries are per-part knobs; reach for one only when a role is
63
63
  right everywhere except one component.
64
64
  5. `anti-ai-tells.json` — 26 shapes that make generated UI look generated, each with the
65
65
  fix. Read before you reach for a gradient hero or a wall of coloured chips.
@@ -145,7 +145,7 @@ has stopped following the brand.
145
145
  |---|---|---|---|
146
146
  | `foundation` | 211 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** Handed a brand colour, this is where it goes: `:root { --primary: <H> <S>% <L>%; }` (HSL components, no `hsl()` wrapper) |
147
147
  | `semantic` | 104 | named roles that follow the seeds — `--ring`, `--text-link`, `--primary-hover`, `--overlay-background` | only when the seed is right and ONE role must differ. That role then stops following a later brand change |
148
- | `component` | 1801 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
148
+ | `component` | 1804 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
149
149
 
150
150
  A token whose `value` is `initial` is not empty and not broken: `initial` is the guaranteed-invalid
151
151
  value, so the real default is computed where the element paints it. Set it and yours wins.
@@ -31,10 +31,25 @@
31
31
  "type": "ReactNode | boolean"
32
32
  },
33
33
  {
34
- "description": "Native img attributes.",
34
+ "description": "antd Image: size the FRAME (number = px, or a CSS length); the picture fills it. `width` alone keeps the picture's ratio; a fixed height crops per `fit`. Also set on the img.",
35
35
  "name": "width / height",
36
36
  "type": "number | string"
37
37
  },
38
+ {
39
+ "description": "Thumbnail frame on the Thumbnail height scale (64 / 96 / 160px) at `--thumbnail-width-ratio` (4 / 3), cropped to fill. godx extension; `width`/`height` override the side they name.",
40
+ "name": "size",
41
+ "type": "\"sm\" | \"md\" | \"lg\""
42
+ },
43
+ {
44
+ "description": "object-fit inside a fixed frame. Default `cover` once the height is fixed (size or height).",
45
+ "name": "fit",
46
+ "type": "\"cover\" | \"contain\""
47
+ },
48
+ {
49
+ "description": "Text under the picture in a figure held to the picture's width — a long file name truncates instead of widening the tile. godx extension.",
50
+ "name": "caption",
51
+ "type": "ReactNode"
52
+ },
38
53
  {
39
54
  "description": "Class on the wrapper (the button).",
40
55
  "name": "className",
@@ -78,7 +93,8 @@
78
93
  "DO pass `preview={false}` for a logo or icon inside that body so it neither opens nor counts.",
79
94
  "DO use `Image.PreviewGroup` or `ImagePreviewGroup` — the same component.",
80
95
  "DON'T hand-roll a lightbox from Dialog + Carousel: it has no zoom, no arrow keys and a 32rem box. That composition is what this replaces.",
81
- "DON'T nest an Image inside a link or button: the thumbnail is itself a button."
96
+ "DON'T nest an Image inside a link or button: the thumbnail is itself a button.",
97
+ "DO size an attachment gallery with `size=\"md\"` (96px, 4:3, cropped) and put the file name in `caption` — never an inline style reading `--thumbnail-block-size`."
82
98
  ],
83
99
  "useCases": [
84
100
  "Screenshots in a test result or wiki page that the reader opens large and pages through.",
@@ -14,6 +14,11 @@
14
14
  "description": "Rail glyph strategy (default 'icon'). 'icon' = legacy look (Plane for current, CheckCircle2 otherwise). 'ordinal' = every glyph is its 1-based step number; status sets colour only. 'status' = glyph by status (done → check, current → filled dot, pending → step number).",
15
15
  "name": "variant",
16
16
  "type": "\"icon\" | \"ordinal\" | \"status\""
17
+ },
18
+ {
19
+ "description": "Item spacing (default 'default'). Sets `--timeline-item-gap` — the space between items and between the rail and the body. 'default' = `--space-stack-md` (16px); 'compact' = `--space-stack-sm` (8px, retune with `--timeline-item-gap-compact`) for a dense activity log / issue history. antd's Timeline has no density prop (one fixed `itemPaddingBottom`, 20px), so this is a godx extension.",
20
+ "name": "density",
21
+ "type": "\"default\" | \"compact\""
17
22
  }
18
23
  ],
19
24
  "related": [
@@ -26,10 +31,11 @@
26
31
  "storyPath": "data-display/Timeline.stories.tsx",
27
32
  "tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a 3-state `status` (done/current/pending) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`.",
28
33
  "usage": [
29
- "DO pass an array of `TimelineItem` objects to `items`. Each item is `{ title, location?, time?, note?, current?, status?, icon? }`. All fields except `title` are optional. The only other prop is `variant`.",
34
+ "DO pass an array of `TimelineItem` objects to `items`. Each item is `{ title, location?, time?, note?, current?, status?, icon? }`. All fields except `title` are optional. The only other props are `variant` and `density`.",
30
35
  "DO mark the in-progress event with `current: true` (or `status: 'current'`). In the default `variant='icon'`, the current item renders a `Plane` icon and every other item a `CheckCircle2`. Use `variant='ordinal'` for a numbered route stepper (1,2,3…) or `variant='status'` for a done→check / current→dot / pending→number tracker. Pass `status: 'done' | 'current' | 'pending'` per item to drive colour and (in the status variant) the glyph; pass `icon` (a LucideIcon) to override the glyph for a single item.",
31
36
  "DO pass `ReactNode` to `title`, `location`, `time`, and `note` — you can embed formatted text, `<Badge>`, `<Badge>`, or `<span>` inside those fields. Use `formatDate` to pre-format timestamps before passing them as `time`.",
32
37
  "DO NOT hand-roll a vertical event list with divs, icons, and connector lines — that is exactly what Timeline ships. Do not apply extra padding or wrapping outside the component; it manages its own rail and spacing internally.",
38
+ "DO pass `density=\"compact\"` when the timeline is a long, dense log (issue history, audit trail) and the default 16px between items reads too airy — it drops the item gap to `--space-stack-sm` (8px). Never tighten it with your own padding/margin on the items.",
33
39
  "DO NOT use Timeline for user-facing wizard progress (steps the user must complete in order) — use `Steps` for that. Timeline is read-only historical/status display; it has no interactive state, no `onClick`, and no concept of 'go to step'.",
34
40
  "DO wrap Timeline in `<CardContent>` when placing it inside a `Card` — bare `Card` has no inner padding, so the rail will render flush against the card edge without `CardContent`.",
35
41
  "THE PROGRESS COLUMN IS ONE HUE (gh#731). The done dot, the current dot and the travelled line all default to `hsl(var(--primary))` — the same contract `Steps` uses for `finish` + `process`, and antd's (only `error` leaves the primary hue). The current item is told apart by a ring (`--timeline-dot-current-ring-width`, `--steps-dot-process-ring-width`'s value), by `aria-current=\"step\"` + a localized sr-only prefix, and by a different glyph — never by a second colour role. Re-theme `--primary` and the whole rail follows. Measured light: done 2.18:1 → 6.31:1 against the card, glyph 6.31:1 on the fill; dark 7.22:1 → 9.84:1, glyph 10.72:1.",
@@ -4974,6 +4974,11 @@
4974
4974
  "description": "Rail glyph strategy (default 'icon'). 'icon' = legacy look (Plane for current, CheckCircle2 otherwise). 'ordinal' = every glyph is its 1-based step number; status sets colour only. 'status' = glyph by status (done → check, current → filled dot, pending → step number).",
4975
4975
  "name": "variant",
4976
4976
  "type": "\"icon\" | \"ordinal\" | \"status\""
4977
+ },
4978
+ {
4979
+ "description": "Item spacing (default 'default'). Sets `--timeline-item-gap` — the space between items and between the rail and the body. 'default' = `--space-stack-md` (16px); 'compact' = `--space-stack-sm` (8px, retune with `--timeline-item-gap-compact`) for a dense activity log / issue history. antd's Timeline has no density prop (one fixed `itemPaddingBottom`, 20px), so this is a godx extension.",
4980
+ "name": "density",
4981
+ "type": "\"default\" | \"compact\""
4977
4982
  }
4978
4983
  ],
4979
4984
  "related": [
@@ -4986,10 +4991,11 @@
4986
4991
  "storyPath": "data-display/Timeline.stories.tsx",
4987
4992
  "tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a 3-state `status` (done/current/pending) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`.",
4988
4993
  "usage": [
4989
- "DO pass an array of `TimelineItem` objects to `items`. Each item is `{ title, location?, time?, note?, current?, status?, icon? }`. All fields except `title` are optional. The only other prop is `variant`.",
4994
+ "DO pass an array of `TimelineItem` objects to `items`. Each item is `{ title, location?, time?, note?, current?, status?, icon? }`. All fields except `title` are optional. The only other props are `variant` and `density`.",
4990
4995
  "DO mark the in-progress event with `current: true` (or `status: 'current'`). In the default `variant='icon'`, the current item renders a `Plane` icon and every other item a `CheckCircle2`. Use `variant='ordinal'` for a numbered route stepper (1,2,3…) or `variant='status'` for a done→check / current→dot / pending→number tracker. Pass `status: 'done' | 'current' | 'pending'` per item to drive colour and (in the status variant) the glyph; pass `icon` (a LucideIcon) to override the glyph for a single item.",
4991
4996
  "DO pass `ReactNode` to `title`, `location`, `time`, and `note` — you can embed formatted text, `<Badge>`, `<Badge>`, or `<span>` inside those fields. Use `formatDate` to pre-format timestamps before passing them as `time`.",
4992
4997
  "DO NOT hand-roll a vertical event list with divs, icons, and connector lines — that is exactly what Timeline ships. Do not apply extra padding or wrapping outside the component; it manages its own rail and spacing internally.",
4998
+ "DO pass `density=\"compact\"` when the timeline is a long, dense log (issue history, audit trail) and the default 16px between items reads too airy — it drops the item gap to `--space-stack-sm` (8px). Never tighten it with your own padding/margin on the items.",
4993
4999
  "DO NOT use Timeline for user-facing wizard progress (steps the user must complete in order) — use `Steps` for that. Timeline is read-only historical/status display; it has no interactive state, no `onClick`, and no concept of 'go to step'.",
4994
5000
  "DO wrap Timeline in `<CardContent>` when placing it inside a `Card` — bare `Card` has no inner padding, so the rail will render flush against the card edge without `CardContent`.",
4995
5001
  "THE PROGRESS COLUMN IS ONE HUE (gh#731). The done dot, the current dot and the travelled line all default to `hsl(var(--primary))` — the same contract `Steps` uses for `finish` + `process`, and antd's (only `error` leaves the primary hue). The current item is told apart by a ring (`--timeline-dot-current-ring-width`, `--steps-dot-process-ring-width`'s value), by `aria-current=\"step\"` + a localized sr-only prefix, and by a different glyph — never by a second colour role. Re-theme `--primary` and the whole rail follows. Measured light: done 2.18:1 → 6.31:1 against the card, glyph 6.31:1 on the fill; dark 7.22:1 → 9.84:1, glyph 10.72:1.",
@@ -15802,10 +15808,25 @@
15802
15808
  "type": "ReactNode | boolean"
15803
15809
  },
15804
15810
  {
15805
- "description": "Native img attributes.",
15811
+ "description": "antd Image: size the FRAME (number = px, or a CSS length); the picture fills it. `width` alone keeps the picture's ratio; a fixed height crops per `fit`. Also set on the img.",
15806
15812
  "name": "width / height",
15807
15813
  "type": "number | string"
15808
15814
  },
15815
+ {
15816
+ "description": "Thumbnail frame on the Thumbnail height scale (64 / 96 / 160px) at `--thumbnail-width-ratio` (4 / 3), cropped to fill. godx extension; `width`/`height` override the side they name.",
15817
+ "name": "size",
15818
+ "type": "\"sm\" | \"md\" | \"lg\""
15819
+ },
15820
+ {
15821
+ "description": "object-fit inside a fixed frame. Default `cover` once the height is fixed (size or height).",
15822
+ "name": "fit",
15823
+ "type": "\"cover\" | \"contain\""
15824
+ },
15825
+ {
15826
+ "description": "Text under the picture in a figure held to the picture's width — a long file name truncates instead of widening the tile. godx extension.",
15827
+ "name": "caption",
15828
+ "type": "ReactNode"
15829
+ },
15809
15830
  {
15810
15831
  "description": "Class on the wrapper (the button).",
15811
15832
  "name": "className",
@@ -15849,7 +15870,8 @@
15849
15870
  "DO pass `preview={false}` for a logo or icon inside that body so it neither opens nor counts.",
15850
15871
  "DO use `Image.PreviewGroup` or `ImagePreviewGroup` — the same component.",
15851
15872
  "DON'T hand-roll a lightbox from Dialog + Carousel: it has no zoom, no arrow keys and a 32rem box. That composition is what this replaces.",
15852
- "DON'T nest an Image inside a link or button: the thumbnail is itself a button."
15873
+ "DON'T nest an Image inside a link or button: the thumbnail is itself a button.",
15874
+ "DO size an attachment gallery with `size=\"md\"` (96px, 4:3, cropped) and put the file name in `caption` — never an inline style reading `--thumbnail-block-size`."
15853
15875
  ],
15854
15876
  "useCases": [
15855
15877
  "Screenshots in a test result or wiki page that the reader opens large and pages through.",
package/agent/index.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "components": 178,
5
5
  "patterns": 21,
6
6
  "rules": 50,
7
- "tokens": 2116,
7
+ "tokens": 2119,
8
8
  "vocabulary": 14
9
9
  },
10
10
  "files": [
@@ -48,19 +48,19 @@
48
48
  "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.",
49
49
  "read": {
50
50
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
51
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.12.0/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.14.0/agent/index.json"
52
52
  },
53
53
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
54
54
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
55
55
  "tokenTiers": {
56
56
  "component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
57
57
  "counts": {
58
- "component": 1801,
58
+ "component": 1804,
59
59
  "foundation": 211,
60
60
  "semantic": 104
61
61
  },
62
62
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
63
63
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
64
64
  },
65
- "version": "31.12.0"
65
+ "version": "31.14.0"
66
66
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 178 components, 2116 design tokens,
4
- > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.12.0.
3
+ > A Japanese-enterprise React design system: 178 components, 2119 design tokens,
4
+ > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.14.0.
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@31.12.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@31.14.0`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -18,7 +18,7 @@ that can only fetch URLs.
18
18
  - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 48 KB — all 178 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
19
  - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–36 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.
20
20
  - [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.2 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
21
- - [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` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 104 `semantic` roles, 1801 `component` knobs.
21
+ - [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` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 104 `semantic` roles, 1804 `component` knobs.
22
22
  - [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.
23
23
  - [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 50 cardinal rules.
24
24
  - [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 26 shapes that make generated UI look generated, each with its fix.
@@ -26,7 +26,7 @@ that can only fetch URLs.
26
26
  ## Pinning
27
27
 
28
28
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
29
- the tag: `.../godx-jp/godxjp-ui/v31.12.0/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v31.14.0/agent/...`. A catalog that does not match the installed
30
30
  package describes props that are absent, or hides props that are present, and says nothing either way.
31
31
 
32
32
  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
@@ -5633,6 +5633,12 @@
5633
5633
  "tier": "component",
5634
5634
  "value": "10rem"
5635
5635
  },
5636
+ {
5637
+ "description": "Image `size` frame: width = the Thumbnail height × this ratio.",
5638
+ "name": "--thumbnail-width-ratio",
5639
+ "tier": "component",
5640
+ "value": "4 / 3"
5641
+ },
5636
5642
  {
5637
5643
  "description": "default = var(--stroke-hairline), resolved at the call site (gh#906)",
5638
5644
  "name": "--thumbnail-border-width",
@@ -5675,6 +5681,18 @@
5675
5681
  "tier": "component",
5676
5682
  "value": "initial"
5677
5683
  },
5684
+ {
5685
+ "description": "Space between items and between rail and body. `initial` so `Timeline density=\"compact\"` can retune it on the root; default = var(--space-stack-md), compact = --timeline-item-gap-compact (default var(--space-stack-sm)), resolved at the call site.",
5686
+ "name": "--timeline-item-gap",
5687
+ "tier": "component",
5688
+ "value": "initial"
5689
+ },
5690
+ {
5691
+ "description": "Data-display component tokens — small-by-design text knobs (rule #45/#46).",
5692
+ "name": "--timeline-item-gap-compact",
5693
+ "tier": "component",
5694
+ "value": "initial"
5695
+ },
5678
5696
  {
5679
5697
  "description": "QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast.",
5680
5698
  "name": "--qr-code-foreground",
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import type { ImagePreviewConfigProp, ImagePreviewGroupProp } from "../../props/components/data-display.prop.js";
3
- export type { ImagePreviewConfigProp, ImagePreviewGroupConfigProp, ImagePreviewGroupProp, ImagePreviewGroupProp as ImagePreviewGroupProps, ImagePreviewItemProp, ImageProp, ImageProp as ImageProps, } from "../../props/components/data-display.prop.js";
3
+ export type { ImageFitProp, ImagePreviewConfigProp, ImagePreviewGroupConfigProp, ImagePreviewGroupProp, ImagePreviewGroupProp as ImagePreviewGroupProps, ImagePreviewItemProp, ImageProp, ImageProp as ImageProps, ImageSizeProp, } from "../../props/components/data-display.prop.js";
4
4
  /**
5
5
  * ImagePreviewGroup — pages one preview across many pictures (antd `Image.PreviewGroup`).
6
6
  *
@@ -15,6 +15,11 @@ export declare const Image: React.ForwardRefExoticComponent<Omit<React.ImgHTMLAt
15
15
  fallback?: string;
16
16
  placeholder?: React.ReactNode | boolean;
17
17
  preview?: boolean | ImagePreviewConfigProp;
18
+ width?: number | string;
19
+ height?: number | string;
20
+ size?: import("./image.js").ImageSizeProp;
21
+ fit?: import("./image.js").ImageFitProp;
22
+ caption?: React.ReactNode;
18
23
  className?: import("../../props/index.js").ClassNameProp;
19
24
  } & React.RefAttributes<HTMLImageElement>> & {
20
25
  PreviewGroup: typeof ImagePreviewGroup;
@@ -386,7 +386,22 @@ function ImagePreviewGroup({
386
386
  ) : null
387
387
  ] }) });
388
388
  }
389
- const ImageRoot = React.forwardRef(function Image({ src, alt, fallback, placeholder, preview = true, className, onLoad, onError, ...imgProps }, ref) {
389
+ const ImageRoot = React.forwardRef(function Image({
390
+ src,
391
+ alt,
392
+ fallback,
393
+ placeholder,
394
+ preview = true,
395
+ width,
396
+ height,
397
+ size,
398
+ fit,
399
+ caption,
400
+ className,
401
+ onLoad,
402
+ onError,
403
+ ...imgProps
404
+ }, ref) {
390
405
  const { t } = useTranslation();
391
406
  const group = React.useContext(ImageGroupContext);
392
407
  const groupDisabled = React.useContext(ImageGroupDisabledContext);
@@ -420,13 +435,26 @@ const ImageRoot = React.forwardRef(function Image({ src, alt, fallback, placehol
420
435
  const [visible, setVisible] = useMergedState(config.visible, false, config.onVisibleChange);
421
436
  const shownSrc = isError && fallback ? fallback : src;
422
437
  const showPlaceholder = placeholder != null && placeholder !== false && status === "loading";
438
+ const sized = size != null || width != null || height != null;
439
+ const frameFit = fit ?? (size != null || height != null ? "cover" : void 0);
440
+ const frame = sized ? {
441
+ "data-sized": "",
442
+ "data-size": size,
443
+ style: {
444
+ ...width != null ? { "--image-inline-size": cssLength(width) } : null,
445
+ ...height != null ? { "--image-block-size": cssLength(height) } : null
446
+ }
447
+ } : null;
423
448
  const picture = /* @__PURE__ */ jsxs(Fragment, { children: [
424
449
  /* @__PURE__ */ jsx(
425
450
  "img",
426
451
  {
427
452
  ...imgProps,
453
+ width,
454
+ height,
428
455
  ref: setImgRef,
429
456
  "data-slot": "image-img",
457
+ "data-fit": frameFit,
430
458
  className: "ui-image-img",
431
459
  src: shownSrc,
432
460
  alt,
@@ -442,59 +470,72 @@ const ImageRoot = React.forwardRef(function Image({ src, alt, fallback, placehol
442
470
  ),
443
471
  showPlaceholder ? /* @__PURE__ */ jsx("span", { "data-slot": "image-placeholder", className: "ui-image-placeholder", "aria-hidden": "true", children: placeholder === true ? null : placeholder }) : null
444
472
  ] });
473
+ const withCaption = (image) => caption == null ? image : /* @__PURE__ */ jsxs("figure", { "data-slot": "image-tile", className: "ui-image-tile", ...frame, children: [
474
+ image,
475
+ /* @__PURE__ */ jsx("figcaption", { "data-slot": "image-caption", className: "ui-image-caption", children: caption })
476
+ ] });
445
477
  if (!canPreview) {
446
- return /* @__PURE__ */ jsx(
447
- "span",
448
- {
449
- ref: wrapperRef,
450
- "data-slot": "image",
451
- "data-status": status,
452
- className: cn("ui-image", className),
453
- children: picture
454
- }
478
+ return withCaption(
479
+ /* @__PURE__ */ jsx(
480
+ "span",
481
+ {
482
+ ref: wrapperRef,
483
+ "data-slot": "image",
484
+ "data-status": status,
485
+ ...frame,
486
+ className: cn("ui-image", className),
487
+ children: picture
488
+ }
489
+ )
455
490
  );
456
491
  }
457
- return /* @__PURE__ */ jsxs(Fragment, { children: [
458
- /* @__PURE__ */ jsxs(
459
- "button",
460
- {
461
- ref: (node) => {
462
- wrapperRef.current = node;
463
- },
464
- type: "button",
465
- "data-slot": "image",
466
- "data-status": status,
467
- "data-preview": "",
468
- className: cn("ui-image ui-focus-ring", className),
469
- "aria-label": alt ? t("dataDisplay.image.previewLabel", { alt }) : t("dataDisplay.image.previewUnnamed"),
470
- "aria-haspopup": "dialog",
471
- onClick: () => inGroup && group ? group.open(id) : setVisible(true),
472
- children: [
473
- picture,
474
- /* @__PURE__ */ jsx("span", { "data-slot": "image-mask", className: "ui-image-mask", "aria-hidden": "true", children: config.mask ?? /* @__PURE__ */ jsxs(Fragment, { children: [
475
- /* @__PURE__ */ jsx(Eye, { className: "ui-image-mask-icon" }),
476
- t("dataDisplay.image.preview")
477
- ] }) })
478
- ]
479
- }
480
- ),
481
- inGroup ? null : /* @__PURE__ */ jsx(
482
- ImagePreviewDialog,
483
- {
484
- open: visible,
485
- onOpenChange: setVisible,
486
- items: [{ src: previewSrc, alt }],
487
- current: 0,
488
- onCurrentChange: () => void 0,
489
- fallback,
490
- scaleStep: config.scaleStep,
491
- minScale: config.minScale,
492
- maxScale: config.maxScale
493
- }
494
- )
495
- ] });
492
+ return withCaption(
493
+ /* @__PURE__ */ jsxs(Fragment, { children: [
494
+ /* @__PURE__ */ jsxs(
495
+ "button",
496
+ {
497
+ ref: (node) => {
498
+ wrapperRef.current = node;
499
+ },
500
+ type: "button",
501
+ "data-slot": "image",
502
+ "data-status": status,
503
+ "data-preview": "",
504
+ ...frame,
505
+ className: cn("ui-image ui-focus-ring", className),
506
+ "aria-label": alt ? t("dataDisplay.image.previewLabel", { alt }) : t("dataDisplay.image.previewUnnamed"),
507
+ "aria-haspopup": "dialog",
508
+ onClick: () => inGroup && group ? group.open(id) : setVisible(true),
509
+ children: [
510
+ picture,
511
+ /* @__PURE__ */ jsx("span", { "data-slot": "image-mask", className: "ui-image-mask", "aria-hidden": "true", children: config.mask ?? /* @__PURE__ */ jsxs(Fragment, { children: [
512
+ /* @__PURE__ */ jsx(Eye, { className: "ui-image-mask-icon" }),
513
+ t("dataDisplay.image.preview")
514
+ ] }) })
515
+ ]
516
+ }
517
+ ),
518
+ inGroup ? null : /* @__PURE__ */ jsx(
519
+ ImagePreviewDialog,
520
+ {
521
+ open: visible,
522
+ onOpenChange: setVisible,
523
+ items: [{ src: previewSrc, alt }],
524
+ current: 0,
525
+ onCurrentChange: () => void 0,
526
+ fallback,
527
+ scaleStep: config.scaleStep,
528
+ minScale: config.minScale,
529
+ maxScale: config.maxScale
530
+ }
531
+ )
532
+ ] })
533
+ );
496
534
  });
497
535
  ImageRoot.displayName = "Image";
536
+ function cssLength(value) {
537
+ return typeof value === "number" ? `${value}px` : value;
538
+ }
498
539
  const Image2 = Object.assign(ImageRoot, { PreviewGroup: ImagePreviewGroup });
499
540
  export {
500
541
  Image2 as Image,
@@ -2,6 +2,11 @@ import type { ReactNode } from "react";
2
2
  import { type LucideIcon } from "lucide-react";
3
3
  export type TimelineStatus = "done" | "current" | "pending";
4
4
  export type TimelineVariant = "icon" | "ordinal" | "status";
5
+ /**
6
+ * A Timeline-local subset of the density vocabulary — it has no `comfortable` step, the same shape
7
+ * as `ListRowDensity`.
8
+ */
9
+ export type TimelineDensity = "default" | "compact";
5
10
  export type TimelineItem = {
6
11
  title: ReactNode;
7
12
  location?: ReactNode;
@@ -21,5 +26,12 @@ export type TimelineProps = {
21
26
  * CheckCircle2 otherwise).
22
27
  */
23
28
  variant?: TimelineVariant;
29
+ /**
30
+ * Space between items (and between the rail and the body): `--timeline-item-gap`. `default` is
31
+ * `--space-stack-md` (16px, today's look); `compact` is `--space-stack-sm` (8px) for a dense
32
+ * activity log or issue history. antd's Timeline has no density/size prop — its item spacing is
33
+ * one fixed token (`itemPaddingBottom`, 20px) — so this is a godx extension, not an antd port.
34
+ */
35
+ density?: TimelineDensity;
24
36
  };
25
- export declare function Timeline({ items, variant }: TimelineProps): import("react").JSX.Element;
37
+ export declare function Timeline({ items, variant, density }: TimelineProps): import("react").JSX.Element;
@@ -16,9 +16,9 @@ function resolveStatus(item) {
16
16
  }
17
17
  return "done";
18
18
  }
19
- function Timeline({ items, variant = "icon" }) {
19
+ function Timeline({ items, variant = "icon", density = "default" }) {
20
20
  const { t } = useTranslation();
21
- return /* @__PURE__ */ jsx("ol", { className: "ui-timeline", "data-variant": variant, children: items.map((item, index) => {
21
+ return /* @__PURE__ */ jsx("ol", { className: "ui-timeline", "data-variant": variant, "data-density": density, children: items.map((item, index) => {
22
22
  const status = resolveStatus(item);
23
23
  const isCurrent = status === "current";
24
24
  const ordinal = index + 1;
@@ -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": "31.12.0",
3
+ "version": "31.14.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,
@@ -1251,8 +1251,35 @@ export type ImageProp = Omit<React.ImgHTMLAttributes<HTMLImageElement>, "alt" |
1251
1251
  placeholder?: React.ReactNode | boolean;
1252
1252
  /** `false` = a plain picture, not clickable, not in its group. Default `true`. */
1253
1253
  preview?: boolean | ImagePreviewConfigProp;
1254
+ /**
1255
+ * antd Image `width`: the frame's inline size (number = px, or any CSS length). The picture
1256
+ * fills it; with no `height` its block size follows the picture's ratio. Also set on the `<img>`.
1257
+ */
1258
+ width?: number | string;
1259
+ /**
1260
+ * antd Image `height`: the frame's block size (number = px, or any CSS length). A fixed height
1261
+ * crops the picture to fill (`fit` defaults to `cover`). Also set on the `<img>`.
1262
+ */
1263
+ height?: number | string;
1264
+ /**
1265
+ * A thumbnail frame on the `Thumbnail` height scale (`--thumbnail-block-size-sm`, `…-size`,
1266
+ * `…-size-lg`: 64 / 96 / 160px) at `--thumbnail-width-ratio` (4 / 3). godx extension — antd has only
1267
+ * `width`/`height`, which override the preset's side they name.
1268
+ */
1269
+ size?: ImageSizeProp;
1270
+ /** How the picture fills a fixed frame (`object-fit`). Default `cover` once the height is fixed. */
1271
+ fit?: ImageFitProp;
1272
+ /**
1273
+ * Text under the picture, held to the picture's width, so a long file name truncates instead of
1274
+ * widening the tile. Renders a `<figure>` with a `<figcaption>`. godx extension.
1275
+ */
1276
+ caption?: React.ReactNode;
1254
1277
  className?: ClassNameProp;
1255
1278
  };
1279
+ /** `Image` thumbnail frame preset — the `Thumbnail` height scale. */
1280
+ export type ImageSizeProp = Extract<SizeProp, "sm" | "md" | "lg">;
1281
+ /** `Image` `fit` — the picture's `object-fit` inside a fixed frame. */
1282
+ export type ImageFitProp = "cover" | "contain";
1256
1283
  /**
1257
1284
  * @see ImagePreviewGroup — pages the preview across a set of pictures (antd `Image.PreviewGroup`).
1258
1285
  *
@@ -2996,7 +2996,7 @@ export declare const COMPONENT_PROP_REGISTRY: {
2996
2996
  readonly TimelineProp: {
2997
2997
  readonly group: "data-display";
2998
2998
  readonly file: "components/data-display/timeline.tsx";
2999
- readonly vocabulary: readonly ["ClassNameProp"];
2999
+ readonly vocabulary: readonly ["ClassNameProp", "DensityProp"];
3000
3000
  };
3001
3001
  readonly TimelineGridColumnProp: {
3002
3002
  readonly group: "data-display";
@@ -4065,6 +4065,16 @@ export declare const COMPONENT_PROP_REGISTRY: {
4065
4065
  readonly reason: "antd PreviewGroup `preview.onChange(current, prevCurrent)`, kept verbatim.";
4066
4066
  }];
4067
4067
  };
4068
+ readonly ImageSizeProp: {
4069
+ readonly group: "data-display";
4070
+ readonly file: "components/data-display.prop.ts";
4071
+ readonly vocabulary: readonly ["SizeProp"];
4072
+ };
4073
+ readonly ImageFitProp: {
4074
+ readonly group: "data-display";
4075
+ readonly file: "components/data-display.prop.ts";
4076
+ readonly vocabulary: readonly [];
4077
+ };
4068
4078
  readonly ImageProp: {
4069
4079
  readonly group: "data-display";
4070
4080
  readonly file: "components/data-display.prop.ts";
@@ -4084,6 +4094,22 @@ export declare const COMPONENT_PROP_REGISTRY: {
4084
4094
  readonly field: "preview";
4085
4095
  readonly local: true;
4086
4096
  readonly reason: "antd Image `preview: boolean | PreviewType` — opt-out or configure the preview.";
4097
+ }, "SizeProp", {
4098
+ readonly field: "width";
4099
+ readonly local: true;
4100
+ readonly reason: "antd Image `width` — the frame's inline size.";
4101
+ }, {
4102
+ readonly field: "height";
4103
+ readonly local: true;
4104
+ readonly reason: "antd Image `height` — the frame's block size.";
4105
+ }, {
4106
+ readonly field: "fit";
4107
+ readonly local: true;
4108
+ readonly reason: "CSS `object-fit` of the picture in a fixed frame.";
4109
+ }, {
4110
+ readonly field: "caption";
4111
+ readonly local: true;
4112
+ readonly reason: "A figcaption held to the picture's width (godx extension).";
4087
4113
  }];
4088
4114
  };
4089
4115
  readonly ImagePreviewGroupProp: {
@@ -3528,7 +3528,7 @@ const COMPONENT_PROP_REGISTRY = {
3528
3528
  TimelineProp: {
3529
3529
  group: "data-display",
3530
3530
  file: "components/data-display/timeline.tsx",
3531
- vocabulary: ["ClassNameProp"]
3531
+ vocabulary: ["ClassNameProp", "DensityProp"]
3532
3532
  },
3533
3533
  TimelineGridColumnProp: {
3534
3534
  group: "data-display",
@@ -4920,6 +4920,16 @@ const COMPONENT_PROP_REGISTRY = {
4920
4920
  }
4921
4921
  ]
4922
4922
  },
4923
+ ImageSizeProp: {
4924
+ group: "data-display",
4925
+ file: "components/data-display.prop.ts",
4926
+ vocabulary: ["SizeProp"]
4927
+ },
4928
+ ImageFitProp: {
4929
+ group: "data-display",
4930
+ file: "components/data-display.prop.ts",
4931
+ vocabulary: []
4932
+ },
4923
4933
  ImageProp: {
4924
4934
  group: "data-display",
4925
4935
  file: "components/data-display.prop.ts",
@@ -4937,6 +4947,15 @@ const COMPONENT_PROP_REGISTRY = {
4937
4947
  field: "preview",
4938
4948
  local: true,
4939
4949
  reason: "antd Image `preview: boolean | PreviewType` \u2014 opt-out or configure the preview."
4950
+ },
4951
+ "SizeProp",
4952
+ { field: "width", local: true, reason: "antd Image `width` \u2014 the frame's inline size." },
4953
+ { field: "height", local: true, reason: "antd Image `height` \u2014 the frame's block size." },
4954
+ { field: "fit", local: true, reason: "CSS `object-fit` of the picture in a fixed frame." },
4955
+ {
4956
+ field: "caption",
4957
+ local: true,
4958
+ reason: "A figcaption held to the picture's width (godx extension)."
4940
4959
  }
4941
4960
  ]
4942
4961
  },
@@ -472,14 +472,18 @@
472
472
  flex-direction: column;
473
473
  }
474
474
 
475
+ .ui-timeline[data-density="compact"] {
476
+ --timeline-item-gap: var(--timeline-item-gap-compact, var(--space-stack-sm));
477
+ }
478
+
475
479
  .ui-timeline-item {
476
480
  display: grid;
477
481
  grid-template-columns: 1.5rem minmax(0, 1fr);
478
- column-gap: var(--space-stack-md);
482
+ column-gap: var(--timeline-item-gap, var(--space-stack-md));
479
483
  }
480
484
 
481
485
  .ui-timeline-body {
482
- padding-bottom: var(--space-stack-md);
486
+ padding-bottom: var(--timeline-item-gap, var(--space-stack-md));
483
487
  }
484
488
 
485
489
  .ui-timeline-item:last-child .ui-timeline-body {
@@ -2683,6 +2687,58 @@
2683
2687
  border-radius: inherit;
2684
2688
  }
2685
2689
 
2690
+ .ui-image[data-sized],
2691
+ .ui-image-tile[data-sized] {
2692
+ inline-size: var(--image-inline-size, auto);
2693
+ }
2694
+
2695
+ .ui-image[data-sized] {
2696
+ block-size: var(--image-block-size, auto);
2697
+ }
2698
+
2699
+ .ui-image[data-sized] > .ui-image-img {
2700
+ inline-size: 100%;
2701
+ max-inline-size: none;
2702
+ block-size: 100%;
2703
+ }
2704
+
2705
+ :is(.ui-image, .ui-image-tile)[data-size="sm"] {
2706
+ --image-block-size: var(--thumbnail-block-size-sm);
2707
+ }
2708
+
2709
+ :is(.ui-image, .ui-image-tile)[data-size="md"] {
2710
+ --image-block-size: var(--thumbnail-block-size);
2711
+ }
2712
+
2713
+ :is(.ui-image, .ui-image-tile)[data-size="lg"] {
2714
+ --image-block-size: var(--thumbnail-block-size-lg);
2715
+ }
2716
+
2717
+ :is(.ui-image, .ui-image-tile)[data-size] {
2718
+ --image-inline-size: calc(var(--image-block-size) * var(--thumbnail-width-ratio));
2719
+ }
2720
+
2721
+ .ui-image-img[data-fit="cover"] {
2722
+ object-fit: cover;
2723
+ }
2724
+
2725
+ .ui-image-img[data-fit="contain"] {
2726
+ object-fit: contain;
2727
+ }
2728
+
2729
+ .ui-image-tile {
2730
+ display: inline-flex;
2731
+ flex-direction: column;
2732
+ gap: var(--space-stack-xs);
2733
+ max-inline-size: 100%;
2734
+ margin: 0;
2735
+ vertical-align: top;
2736
+ }
2737
+
2738
+ .ui-image-caption {
2739
+ min-inline-size: 0;
2740
+ }
2741
+
2686
2742
  .ui-image-placeholder {
2687
2743
  position: absolute;
2688
2744
  inset: 0;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "31.12.0",
2
+ "version": "31.14.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",
@@ -169,6 +169,7 @@
169
169
  --thumbnail-block-size-sm: 4rem;
170
170
  --thumbnail-block-size: 6rem;
171
171
  --thumbnail-block-size-lg: 10rem;
172
+ --thumbnail-width-ratio: 4 / 3;
172
173
  --thumbnail-border-width: initial;
173
174
  --thumbnail-radius: initial;
174
175
 
@@ -179,6 +180,9 @@
179
180
 
180
181
  --timeline-dot-current-ring-width: initial;
181
182
 
183
+ --timeline-item-gap: initial;
184
+ --timeline-item-gap-compact: initial;
185
+
182
186
  --qr-code-foreground: 0 0% 0%;
183
187
  --qr-code-background: 0 0% 100%;
184
188
  --qr-code-size-xs: 6rem;
@@ -399,6 +399,18 @@ bottomEnd | none` (`TablePaginationPositionProp`, default `['bottomEnd']`), the
399
399
  would rule every existing tree, including the picker-sized and `size="xs"` outlines where a rule
400
400
  every 24px is noise rather than structure. A tree used as page navigation opts in.
401
401
 
402
+ - **`Image size` / `fit` / `caption` have no antd counterpart.** antd's Image takes `width` /
403
+ `height` on the frame and the picture, and those are ported as antd has them (a fixed height
404
+ crops with `fit`, default `cover`, where antd stretches). godx adds `size?: "sm" | "md" | "lg"`
405
+ — the Thumbnail height scale at `--thumbnail-width-ratio` (4 / 3) — `fit?: "cover" | "contain"`,
406
+ and `caption`, a figcaption held to the picture's width so a file name truncates under it.
407
+
408
+ - **`Timeline density` has no antd counterpart.** antd's Timeline spaces items with one fixed
409
+ token (`itemPaddingBottom`, 20px) and offers no size or density prop. godx adds
410
+ `density?: "default" | "compact"` — the ListRow-shaped subset of `DensityProp` — driving one
411
+ token, `--timeline-item-gap`: `default` keeps `--space-stack-md` (16px), `compact` is
412
+ `--space-stack-sm` (8px), the step nearer antd's 20px of the two compact candidates (xs is 4px).
413
+
402
414
  - **`RangeTimeline` nested rows take `Tree`'s spelling, not antd's (gh#724).** antd has no Gantt;
403
415
  the nearest names are `Tree expandedKeys` / `defaultExpandedKeys` / `onExpand` and `Table
404
416
  expandable.expandedRowKeys` / `onExpandedRowsChange`. This library already mapped the Tree ones
@@ -212,7 +212,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
212
212
  | Actions | ✓ | · | N/A | N/A | · | · | · | · | · | · |
213
213
  | ThoughtChain | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
214
214
  | OrgChart | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
215
- | Image | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
215
+ | Image | ✓ | · | N/A | N/A | · | · | · | · | · | · |
216
216
  | Attachments | ✓ | N/A | · | N/A | · | · | · | · | · | · |
217
217
  | Marquee | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
218
218
  | Masonry | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
@@ -124,6 +124,46 @@ export default function Demo() {
124
124
  </Flex>
125
125
  </CardContent>
126
126
  </Card>
127
+
128
+ <Card>
129
+ <CardHeader>
130
+ <CardTitle level={2}>Thumbnail size · fit · caption</CardTitle>
131
+ <CardDescription>
132
+ size puts the frame on the Thumbnail height scale (sm 64px, md 96px, lg 160px) at 4:3,
133
+ cropped to fill; fit=&quot;contain&quot; letterboxes instead. caption keeps a long
134
+ file name under the picture, truncated at the picture&apos;s width.
135
+ </CardDescription>
136
+ </CardHeader>
137
+ <CardContent>
138
+ <ImagePreviewGroup>
139
+ <Flex gap="sm" wrap align="start">
140
+ <Image
141
+ src={shotLandscape}
142
+ size="md"
143
+ alt="Dashboard"
144
+ caption={
145
+ <Text size="xs" truncate>
146
+ dashboard-after-the-fix-2026-09-30-full-resolution.png
147
+ </Text>
148
+ }
149
+ />
150
+ <Image
151
+ src={shotPortrait}
152
+ size="md"
153
+ fit="contain"
154
+ alt="List screen"
155
+ caption={
156
+ <Text size="xs" truncate>
157
+ list-screen.png
158
+ </Text>
159
+ }
160
+ />
161
+ <Image src={coverTerrain} size="sm" alt="Cover image" />
162
+ <Image src={portraitIris} size="lg" alt="Portrait" />
163
+ </Flex>
164
+ </ImagePreviewGroup>
165
+ </CardContent>
166
+ </Card>
127
167
  </Flex>
128
168
  </PageContainer>
129
169
  );
@@ -126,6 +126,19 @@ export default function Demo() {
126
126
  </CardContent>
127
127
  </Card>
128
128
 
129
+ <Card>
130
+ <CardHeader>
131
+ <CardTitle level={2}>density=&quot;compact&quot;</CardTitle>
132
+ <CardDescription>
133
+ Item gap drops to --space-stack-sm (8px) for a long log such as an issue history. antd
134
+ has no Timeline density; this is a godx extension.
135
+ </CardDescription>
136
+ </CardHeader>
137
+ <CardContent>
138
+ <Timeline variant="status" density="compact" items={approvalItems} />
139
+ </CardContent>
140
+ </Card>
141
+
129
142
  <Card>
130
143
  <CardHeader>
131
144
  <CardTitle level={2}>配送ルート進捗</CardTitle>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "31.12.0",
4
- "godxUiMcp": "31.12.0",
3
+ "version": "31.14.0",
4
+ "godxUiMcp": "31.14.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",