@godxjp/ui 31.8.0 → 31.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.8.0.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 31.9.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.
@@ -32,6 +32,12 @@
32
32
  "description": "Take the remaining height of the parent instead of growing with the content. Default false keeps the pane content-height, which is right inside a scrolling page. Set it for an app-shell surface whose columns own their own scrolling — a chat transcript with a pinned composer, a full-height table beside a detail rail: the pane and both columns get a DEFINITE height, so overflow inside a column scrolls that column instead of pushing the page taller. The parent still decides how much height there is to fill: inside a flex column, give the wrapper `flex: 1; min-height: 0`.",
33
33
  "name": "fill",
34
34
  "type": "boolean"
35
+ },
36
+ {
37
+ "defaultValue": "\"main-first\"",
38
+ "description": "Which pane comes first when the pane is too narrow to split and the columns stack. `aside-first` puts the aside on top — a detail page whose properties card must not fall below a long comment thread on a phone. It moves the `<aside>` first in the DOM (not a CSS `order` swap), so in the stacked layout reading and Tab order match the screen (WCAG 1.3.2 / 2.4.3); once the pane splits, grid placement keeps main in the leading column and the aside inline-end, geometry identical to the default. No antd equivalent (antd's closest tool is Grid `Col order`, a visual-only reorder).",
39
+ "name": "stackOrder",
40
+ "type": "\"main-first\" | \"aside-first\""
35
41
  }
36
42
  ],
37
43
  "related": [
@@ -48,11 +54,12 @@
48
54
  "usage": [
49
55
  "DO: pass all right-panel content via the `aside` prop — it renders inside a semantic `<aside>` element at a fixed rem width (sm=20rem, md=22rem). The `children` prop fills the main `1fr` column. Both accept any ReactNode.",
50
56
  "DO: reach for `fill` when the pane is an app-shell surface rather than a block in a scrolling page — a chat transcript whose composer stays pinned to the bottom, a full-height table beside a detail rail. It is the supported way to say \"be as tall as what is left\"; a consumer that instead styles the pane's wrapper divs from outside (`[&>*]`, `[&>*>*]`) is targeting this component's internal DOM by position and will break the day another wrapper appears.",
57
+ "DO: set `stackOrder=\"aside-first\"` when the aside is what a phone user needs first — an issue's properties / agent card above its comment thread. DON'T render the aside twice (once above, once beside) or reorder with a CSS `order` override: the prop already puts it first in the DOM, so focus order follows the stacked screen, and restores the side-by-side placement once the pane splits.",
51
58
  "DO: choose `asideWidth=\"sm\"` for compact detail panels (filters, quick stats, key-value summaries) and the default `asideWidth=\"md\"` for richer panels (forms, timelines, long metadata lists).",
52
59
  "DO: wrap SplitPane inside `PageContainer` or `PageContainer.Inset` — SplitPane provides no page padding of its own. It is a grid primitive, not a page scaffold.",
53
60
  "DO: close a collapsible rail with `aside={null}` — a Slack-style thread, a Linear-style detail panel — rather than swapping `<SplitPane aside={<Thread />}>{page}</SplitPane>` for a bare `{page}`. Both look the same on screen; only the prop keeps `children` mounted. The conditional swap changes the DEPTH of `{page}` in the React tree, so React remounts it: measured in a consumer as a message list jumping from scrollTop 400 to the bottom the instant a thread opened, losing the reader's place and any component state below it.",
54
61
  "DON'T: leave `SplitPane` mounted with an empty `aside={<div />}` to avoid that remount — an empty aside still reserves its rail, leaving a blank 20-30rem column. `aside={null}` removes the element AND the column (the pane sets `data-aside=\"closed\"` on itself, so the collapse is one CSS rule, not a call-site width override).",
55
- "DON'T: expect two columns below 48rem OF THE PANE'S OWN WIDTH (64rem for `asideWidth=\"lg\"`). The split is a CSS container query on the pane, not a viewport media query, so a narrow embedded pane on a large screen correctly stays single-column and a wide pane on a small screen still splits. Below the threshold it stacks (main on top, aside below); for layouts that must stay side-by-side at any width use CSS Grid or `ResponsiveGrid`.",
62
+ "DON'T: expect two columns below 48rem OF THE PANE'S OWN WIDTH (64rem for `asideWidth=\"lg\"`). The split is a CSS container query on the pane, not a viewport media query, so a narrow embedded pane on a large screen correctly stays single-column and a wide pane on a small screen still splits. Below the threshold it stacks (main on top, aside below — `stackOrder=\"aside-first\"` flips that); for layouts that must stay side-by-side at any width use CSS Grid or `ResponsiveGrid`.",
56
63
  "DON'T: add a CSS `overflow: hidden` or fixed height on the SplitPane wrapper; both columns carry `min-width: 0` to handle overflow correctly, and the grid uses `minmax(0, 1fr)` — adding external constraints will break the overflow contract.",
57
64
  "DON'T: hand-roll a two-column div layout with flexbox or CSS Grid when SplitPane already ships — that duplicates the responsive breakpoint logic and the semantic `<aside>` element."
58
65
  ],
@@ -2070,6 +2070,12 @@
2070
2070
  "description": "Take the remaining height of the parent instead of growing with the content. Default false keeps the pane content-height, which is right inside a scrolling page. Set it for an app-shell surface whose columns own their own scrolling — a chat transcript with a pinned composer, a full-height table beside a detail rail: the pane and both columns get a DEFINITE height, so overflow inside a column scrolls that column instead of pushing the page taller. The parent still decides how much height there is to fill: inside a flex column, give the wrapper `flex: 1; min-height: 0`.",
2071
2071
  "name": "fill",
2072
2072
  "type": "boolean"
2073
+ },
2074
+ {
2075
+ "defaultValue": "\"main-first\"",
2076
+ "description": "Which pane comes first when the pane is too narrow to split and the columns stack. `aside-first` puts the aside on top — a detail page whose properties card must not fall below a long comment thread on a phone. It moves the `<aside>` first in the DOM (not a CSS `order` swap), so in the stacked layout reading and Tab order match the screen (WCAG 1.3.2 / 2.4.3); once the pane splits, grid placement keeps main in the leading column and the aside inline-end, geometry identical to the default. No antd equivalent (antd's closest tool is Grid `Col order`, a visual-only reorder).",
2077
+ "name": "stackOrder",
2078
+ "type": "\"main-first\" | \"aside-first\""
2073
2079
  }
2074
2080
  ],
2075
2081
  "related": [
@@ -2086,11 +2092,12 @@
2086
2092
  "usage": [
2087
2093
  "DO: pass all right-panel content via the `aside` prop — it renders inside a semantic `<aside>` element at a fixed rem width (sm=20rem, md=22rem). The `children` prop fills the main `1fr` column. Both accept any ReactNode.",
2088
2094
  "DO: reach for `fill` when the pane is an app-shell surface rather than a block in a scrolling page — a chat transcript whose composer stays pinned to the bottom, a full-height table beside a detail rail. It is the supported way to say \"be as tall as what is left\"; a consumer that instead styles the pane's wrapper divs from outside (`[&>*]`, `[&>*>*]`) is targeting this component's internal DOM by position and will break the day another wrapper appears.",
2095
+ "DO: set `stackOrder=\"aside-first\"` when the aside is what a phone user needs first — an issue's properties / agent card above its comment thread. DON'T render the aside twice (once above, once beside) or reorder with a CSS `order` override: the prop already puts it first in the DOM, so focus order follows the stacked screen, and restores the side-by-side placement once the pane splits.",
2089
2096
  "DO: choose `asideWidth=\"sm\"` for compact detail panels (filters, quick stats, key-value summaries) and the default `asideWidth=\"md\"` for richer panels (forms, timelines, long metadata lists).",
2090
2097
  "DO: wrap SplitPane inside `PageContainer` or `PageContainer.Inset` — SplitPane provides no page padding of its own. It is a grid primitive, not a page scaffold.",
2091
2098
  "DO: close a collapsible rail with `aside={null}` — a Slack-style thread, a Linear-style detail panel — rather than swapping `<SplitPane aside={<Thread />}>{page}</SplitPane>` for a bare `{page}`. Both look the same on screen; only the prop keeps `children` mounted. The conditional swap changes the DEPTH of `{page}` in the React tree, so React remounts it: measured in a consumer as a message list jumping from scrollTop 400 to the bottom the instant a thread opened, losing the reader's place and any component state below it.",
2092
2099
  "DON'T: leave `SplitPane` mounted with an empty `aside={<div />}` to avoid that remount — an empty aside still reserves its rail, leaving a blank 20-30rem column. `aside={null}` removes the element AND the column (the pane sets `data-aside=\"closed\"` on itself, so the collapse is one CSS rule, not a call-site width override).",
2093
- "DON'T: expect two columns below 48rem OF THE PANE'S OWN WIDTH (64rem for `asideWidth=\"lg\"`). The split is a CSS container query on the pane, not a viewport media query, so a narrow embedded pane on a large screen correctly stays single-column and a wide pane on a small screen still splits. Below the threshold it stacks (main on top, aside below); for layouts that must stay side-by-side at any width use CSS Grid or `ResponsiveGrid`.",
2100
+ "DON'T: expect two columns below 48rem OF THE PANE'S OWN WIDTH (64rem for `asideWidth=\"lg\"`). The split is a CSS container query on the pane, not a viewport media query, so a narrow embedded pane on a large screen correctly stays single-column and a wide pane on a small screen still splits. Below the threshold it stacks (main on top, aside below — `stackOrder=\"aside-first\"` flips that); for layouts that must stay side-by-side at any width use CSS Grid or `ResponsiveGrid`.",
2094
2101
  "DON'T: add a CSS `overflow: hidden` or fixed height on the SplitPane wrapper; both columns carry `min-width: 0` to handle overflow correctly, and the grid uses `minmax(0, 1fr)` — adding external constraints will break the overflow contract.",
2095
2102
  "DON'T: hand-roll a two-column div layout with flexbox or CSS Grid when SplitPane already ships — that duplicates the responsive breakpoint logic and the semantic `<aside>` element."
2096
2103
  ],
package/agent/index.json CHANGED
@@ -48,7 +48,7 @@
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.8.0/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.9.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",
@@ -62,5 +62,5 @@
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.8.0"
65
+ "version": "31.9.0"
66
66
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
3
  > A Japanese-enterprise React design system: 177 components, 2104 design tokens,
4
- > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.8.0.
4
+ > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.9.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.8.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@31.9.0`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -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.8.0/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v31.9.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.
@@ -28,5 +28,20 @@ export type SplitPaneProps = {
28
28
  * not invent it. Inside a flex column give the wrapper `flex: 1; min-height: 0`.
29
29
  */
30
30
  fill?: boolean;
31
+ /**
32
+ * Which pane comes first when the pane is too narrow to split and the columns STACK.
33
+ *
34
+ * `main-first` (default) keeps today's order: main on top, aside below. `aside-first` puts the
35
+ * aside on top — for a detail page whose properties / agent card must not fall below a long
36
+ * comment thread on a phone.
37
+ *
38
+ * `aside-first` moves the `<aside>` FIRST IN THE DOM, not just visually, so in the stacked
39
+ * layout the reading and focus order match what is on screen (WCAG 1.3.2 / 2.4.3 — a CSS
40
+ * `order` swap would leave Tab going through the whole main column before reaching the aside
41
+ * drawn above it). Once the pane is wide enough to split, grid placement puts main back in the
42
+ * leading column and the aside in the trailing one (inline-end, so it flips under RTL); there
43
+ * the DOM order is aside → main, which reads as two independent regions, not a broken sequence.
44
+ */
45
+ stackOrder?: "main-first" | "aside-first";
31
46
  };
32
- export declare function SplitPane({ children, aside, asideWidth, asideLabel, fill, }: SplitPaneProps): import("react").JSX.Element;
47
+ export declare function SplitPane({ children, aside, asideWidth, asideLabel, fill, stackOrder, }: SplitPaneProps): import("react").JSX.Element;
@@ -4,9 +4,12 @@ function SplitPane({
4
4
  aside,
5
5
  asideWidth = "md",
6
6
  asideLabel,
7
- fill = false
7
+ fill = false,
8
+ stackOrder = "main-first"
8
9
  }) {
9
10
  const closed = aside === null || aside === void 0;
11
+ const asideFirst = stackOrder === "aside-first";
12
+ const asideElement = closed ? null : /* @__PURE__ */ jsx("aside", { className: "ui-split-pane-aside", "aria-label": asideLabel, children: aside });
10
13
  return (
11
14
  // `data-fill` is published on BOTH wrappers, not just the grid: the scope element is the one
12
15
  // the consumer's own box actually contains, so without it the height stops at the scope and
@@ -19,9 +22,11 @@ function SplitPane({
19
22
  "data-aside-width": asideWidth,
20
23
  "data-aside": closed ? "closed" : void 0,
21
24
  "data-fill": fill ? "true" : void 0,
25
+ "data-stack-order": asideFirst ? "aside-first" : void 0,
22
26
  children: [
27
+ asideFirst ? asideElement : null,
23
28
  /* @__PURE__ */ jsx("div", { className: "ui-split-pane-main", children }),
24
- closed ? null : /* @__PURE__ */ jsx("aside", { className: "ui-split-pane-aside", "aria-label": asideLabel, children: aside })
29
+ asideFirst ? null : asideElement
25
30
  ]
26
31
  }
27
32
  ) })
@@ -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.8.0",
3
+ "version": "31.9.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "31.8.0",
2
+ "version": "31.9.0",
3
3
  "generatedBy": "scripts/gen-style-layers.mjs — do not edit by hand; run `pnpm gen:style-layers`",
4
4
  "base": "base.css",
5
5
  "fonts": "fonts.css",
@@ -608,6 +608,10 @@
608
608
  min-block-size: 0;
609
609
  }
610
610
 
611
+ .ui-split-pane[data-fill="true"][data-stack-order="aside-first"]:not([data-aside="closed"]) {
612
+ grid-template-rows: auto minmax(0, 1fr);
613
+ }
614
+
611
615
  .ui-resizable-panel-group {
612
616
  display: flex;
613
617
  min-height: 0;
@@ -662,12 +666,53 @@
662
666
  .ui-split-pane[data-aside-width="md"] {
663
667
  grid-template-columns: minmax(0, 1fr) 22rem;
664
668
  }
669
+
670
+ .ui-split-pane[data-stack-order="aside-first"]:is(
671
+ [data-aside-width="sm"],
672
+ [data-aside-width="md"]
673
+ )
674
+ > .ui-split-pane-main {
675
+ grid-column: 1;
676
+ grid-row: 1;
677
+ }
678
+
679
+ .ui-split-pane[data-stack-order="aside-first"]:is(
680
+ [data-aside-width="sm"],
681
+ [data-aside-width="md"]
682
+ )
683
+ > .ui-split-pane-aside {
684
+ grid-column: 2;
685
+ grid-row: 1;
686
+ }
687
+
688
+ .ui-split-pane[data-fill="true"][data-stack-order="aside-first"]:is(
689
+ [data-aside-width="sm"],
690
+ [data-aside-width="md"]
691
+ ):not([data-aside="closed"]) {
692
+ grid-template-rows: minmax(0, 1fr);
693
+ }
665
694
  }
666
695
 
667
696
  @container split-pane (min-width: 64rem) {
668
697
  .ui-split-pane[data-aside-width="lg"] {
669
698
  grid-template-columns: minmax(0, 1fr) 30rem;
670
699
  }
700
+
701
+ .ui-split-pane[data-stack-order="aside-first"][data-aside-width="lg"] > .ui-split-pane-main {
702
+ grid-column: 1;
703
+ grid-row: 1;
704
+ }
705
+
706
+ .ui-split-pane[data-stack-order="aside-first"][data-aside-width="lg"] > .ui-split-pane-aside {
707
+ grid-column: 2;
708
+ grid-row: 1;
709
+ }
710
+
711
+ .ui-split-pane[data-fill="true"][data-stack-order="aside-first"][data-aside-width="lg"]:not(
712
+ [data-aside="closed"]
713
+ ) {
714
+ grid-template-rows: minmax(0, 1fr);
715
+ }
671
716
  }
672
717
 
673
718
  .ui-split-pane-main,
@@ -561,6 +561,16 @@
561
561
  "presetsOnlyDescription": "panelRender returns only Presets — people pick from the palette and cannot type an arbitrary hex.",
562
562
  "tagColorLabel": "Tag colour",
563
563
  "tagColorHelper": "Arrow keys move between swatches; the selected one is announced."
564
+ },
565
+ "splitPaneStackOrder": {
566
+ "title": "stackOrder — the aside first when the panes stack",
567
+ "lead": "stackOrder=\"aside-first\" puts the aside above main once the pane is too narrow to split, and first in the DOM, so Tab reaches it first too. Wide enough to split, it sits in the trailing column exactly as it does by default. The embed below is narrower than the split width, so the properties card sits above the thread.",
568
+ "propertiesTitle": "Properties",
569
+ "status": "Status",
570
+ "statusValue": "In progress",
571
+ "assignee": "Assignee",
572
+ "threadTitle": "Comments",
573
+ "threadLead": "Long threads no longer push the properties below the fold on a phone."
564
574
  }
565
575
  },
566
576
  "serviceLauncherShowcase": {
@@ -555,6 +555,16 @@
555
555
  "presetsOnlyDescription": "panelRender で Presets だけを返すと、パレットからの選択のみになり任意の HEX は入力できない。",
556
556
  "tagColorLabel": "タグの色",
557
557
  "tagColorHelper": "矢印キーでスウォッチ間を移動し、選択中の色が読み上げられる。"
558
+ },
559
+ "splitPaneStackOrder": {
560
+ "title": "stackOrder — 縦積み時にサイドを先頭へ",
561
+ "lead": "stackOrder=\"aside-first\" は、分割できない幅になったときにサイドをメインの上に置き、DOM でも先頭にするため Tab でも先に届きます。分割できる幅では既定と同じく後方の列に並びます。下の埋め込みは分割幅より狭いため、プロパティがスレッドの上に来ます。",
562
+ "propertiesTitle": "プロパティ",
563
+ "status": "ステータス",
564
+ "statusValue": "対応中",
565
+ "assignee": "担当者",
566
+ "threadTitle": "コメント",
567
+ "threadLead": "スマートフォンでも、長いスレッドがプロパティを画面外へ押し出しません。"
558
568
  }
559
569
  },
560
570
  "serviceLauncherShowcase": {
@@ -555,6 +555,16 @@
555
555
  "presetsOnlyDescription": "panelRender chỉ trả về Presets — người dùng chọn trong bảng màu, không gõ được mã hex tuỳ ý.",
556
556
  "tagColorLabel": "Màu thẻ",
557
557
  "tagColorHelper": "Phím mũi tên di chuyển giữa các ô màu; ô đang chọn được đọc lên."
558
+ },
559
+ "splitPaneStackOrder": {
560
+ "title": "stackOrder — đưa cột phụ lên trước khi xếp chồng",
561
+ "lead": "stackOrder=\"aside-first\" đặt cột phụ lên trên cột chính khi khung quá hẹp để chia đôi, và đặt nó trước trong DOM nên phím Tab cũng tới nó trước. Khi đủ rộng để chia, nó nằm ở cột cuối như mặc định. Khung nhúng bên dưới hẹp hơn ngưỡng chia đôi, nên thẻ thuộc tính nằm trên luồng bình luận.",
562
+ "propertiesTitle": "Thuộc tính",
563
+ "status": "Trạng thái",
564
+ "statusValue": "Đang xử lý",
565
+ "assignee": "Người phụ trách",
566
+ "threadTitle": "Bình luận",
567
+ "threadLead": "Trên điện thoại, luồng bình luận dài không còn đẩy phần thuộc tính xuống dưới."
558
568
  }
559
569
  },
560
570
  "serviceLauncherShowcase": {
@@ -10,6 +10,7 @@ import {
10
10
  StatCard,
11
11
  } from "@godxjp/ui/data-display";
12
12
  import { Button, Text } from "@godxjp/ui/general";
13
+ import { useTranslation } from "@godxjp/ui/i18n";
13
14
  import { FileText, Building2, Calendar, CreditCard, ArrowRight } from "lucide-react";
14
15
 
15
16
  /**
@@ -98,6 +99,7 @@ const yen = (n: number) => yenFormat.format(Math.round(n));
98
99
  const toNumber = (amount: string) => Number(amount.replace(/[^0-9]/g, ""));
99
100
 
100
101
  export default function Demo() {
102
+ const { t } = useTranslation();
101
103
  const [selectedId, setSelectedId] = useState<string>("INV-0241");
102
104
  const [asideWidth, setAsideWidth] = useState<"sm" | "md">("md");
103
105
  const [threadOpen, setThreadOpen] = useState(true);
@@ -406,6 +408,69 @@ export default function Demo() {
406
408
  </CardContent>
407
409
  </Card>
408
410
 
411
+ {/* ── stackOrder="aside-first" (gh#1057) — the aside stacks ABOVE main when narrow ── */}
412
+ <Card>
413
+ <CardHeader>
414
+ <CardTitle level={2}>{t("showcase.splitPaneStackOrder.title")}</CardTitle>
415
+ <CardDescription>{t("showcase.splitPaneStackOrder.lead")}</CardDescription>
416
+ </CardHeader>
417
+ <CardContent>
418
+ <Card className="max-w-md">
419
+ <CardContent>
420
+ <SplitPane
421
+ stackOrder="aside-first"
422
+ asideWidth="sm"
423
+ asideLabel={t("showcase.splitPaneStackOrder.propertiesTitle")}
424
+ aside={
425
+ <Card variant="muted">
426
+ <CardHeader>
427
+ <CardTitle level={3} className="text-sm">
428
+ {t("showcase.splitPaneStackOrder.propertiesTitle")}
429
+ </CardTitle>
430
+ </CardHeader>
431
+ <CardContent>
432
+ <Flex direction="col" gap="xs">
433
+ <Flex direction="row" justify="between" gap="sm">
434
+ <Text tone="muted">{t("showcase.splitPaneStackOrder.status")}</Text>
435
+ <Badge tone="info">
436
+ {t("showcase.splitPaneStackOrder.statusValue")}
437
+ </Badge>
438
+ </Flex>
439
+ <Flex direction="row" justify="between" gap="sm">
440
+ <Text tone="muted">{t("showcase.splitPaneStackOrder.assignee")}</Text>
441
+ <Text>{THREAD_REPLIES[0].author}</Text>
442
+ </Flex>
443
+ </Flex>
444
+ </CardContent>
445
+ </Card>
446
+ }
447
+ >
448
+ <Card>
449
+ <CardHeader>
450
+ <CardTitle level={3} className="text-sm">
451
+ {t("showcase.splitPaneStackOrder.threadTitle")}
452
+ </CardTitle>
453
+ <CardDescription>
454
+ {t("showcase.splitPaneStackOrder.threadLead")}
455
+ </CardDescription>
456
+ </CardHeader>
457
+ <CardContent>
458
+ <Flex direction="col" gap="sm">
459
+ {CHANNEL_MESSAGES.map((message) => (
460
+ <Flex key={message.author} direction="col" gap="xs">
461
+ <Text weight="medium">{message.author}</Text>
462
+ <Text tone="muted">{message.body}</Text>
463
+ </Flex>
464
+ ))}
465
+ </Flex>
466
+ </CardContent>
467
+ </Card>
468
+ </SplitPane>
469
+ </CardContent>
470
+ </Card>
471
+ </CardContent>
472
+ </Card>
473
+
409
474
  <Card>
410
475
  <CardHeader>
411
476
  <CardTitle level={2}>Container stress · narrow vs wide embed</CardTitle>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "31.8.0",
4
- "godxUiMcp": "31.8.0",
3
+ "version": "31.9.0",
4
+ "godxUiMcp": "31.9.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",