@godxjp/ui 28.7.0 → 28.8.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.
- package/dist/components/data-display/index.d.ts +2 -0
- package/dist/components/data-display/index.js +2 -0
- package/dist/components/data-display/marquee.d.ts +16 -0
- package/dist/components/data-display/marquee.js +155 -0
- package/dist/components/general/reveal.d.ts +23 -2
- package/dist/components/general/reveal.js +37 -7
- package/dist/components/general/typography.d.ts +4 -1
- package/dist/components/general/typography.js +14 -1
- package/dist/components/layout/affix.d.ts +86 -0
- package/dist/components/layout/affix.js +187 -0
- package/dist/components/layout/index.d.ts +4 -0
- package/dist/components/layout/index.js +4 -0
- package/dist/components/layout/legal-document-shell.js +4 -3
- package/dist/components/layout/masonry.d.ts +74 -0
- package/dist/components/layout/masonry.js +214 -0
- package/dist/components/layout/page-container.js +5 -20
- package/dist/components/navigation/anchor.d.ts +64 -0
- package/dist/components/navigation/anchor.js +284 -0
- package/dist/components/navigation/index.d.ts +4 -0
- package/dist/components/navigation/index.js +4 -0
- package/dist/components/navigation/mega-menu.d.ts +21 -0
- package/dist/components/navigation/mega-menu.js +526 -0
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +517 -0
- package/dist/i18n/messages/ja.json +513 -0
- package/dist/i18n/messages/vi.json +513 -0
- package/dist/lib/hooks.d.ts +68 -0
- package/dist/lib/hooks.js +52 -0
- package/dist/lib/platform.d.ts +14 -0
- package/dist/lib/platform.js +10 -1
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.js +3 -2
- package/dist/props/components/data-display.prop.d.ts +95 -1
- package/dist/props/components/general.prop.d.ts +47 -3
- package/dist/props/components/layout.prop.d.ts +194 -0
- package/dist/props/components/navigation.prop.d.ts +263 -0
- package/dist/props/registry.d.ts +359 -4
- package/dist/props/registry.js +472 -3
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/props/vocabulary/interaction.prop.d.ts +39 -2
- package/dist/styles/control.css +5 -6
- package/dist/styles/data-display-layout.css +2 -1
- package/dist/styles/density.css +4 -0
- package/dist/styles/layout.css +79 -0
- package/dist/styles/motion.css +121 -1
- package/dist/styles/navigation-layout.css +397 -1
- package/dist/styles/shell-layout.css +3 -0
- package/dist/styles/text-layout.css +52 -4
- package/dist/tokens/base.css +5 -0
- package/dist/tokens/components/affix.css +7 -0
- package/dist/tokens/components/anchor.css +17 -0
- package/dist/tokens/components/control.css +3 -3
- package/dist/tokens/components/form.css +1 -1
- package/dist/tokens/components/marquee.css +7 -0
- package/dist/tokens/components/masonry.css +6 -0
- package/dist/tokens/components/mega-menu.css +62 -0
- package/dist/tokens/components/shell.css +3 -0
- package/dist/tokens/foundation.css +11 -0
- package/dist/tokens/semantic/layout.css +7 -0
- package/docs/COMPOSITION-VS-COMPONENT.md +19 -1
- package/docs/DESIGN-AUTHORITY.md +99 -18
- package/docs/FRAME-COVERAGE-REPORT.md +7 -2
- package/docs/data-display/marquee.tsx +254 -0
- package/docs/foundation/_theme-editor-scope.ts +222 -0
- package/docs/foundation/density.tsx +12 -2
- package/docs/foundation/spacing.tsx +5 -0
- package/docs/foundation/theme-editor.tsx +645 -0
- package/docs/general/activity.tsx +65 -0
- package/docs/general/reveal.tsx +290 -22
- package/docs/general/typography.tsx +91 -1
- package/docs/layout/affix.tsx +209 -0
- package/docs/layout/masonry.tsx +291 -0
- package/docs/navigation/anchor.tsx +285 -0
- package/docs/navigation/mega-menu-panel.tsx +86 -0
- package/docs/navigation/mega-menu.tsx +254 -0
- package/docs/roadmap/website-components.md +779 -0
- package/docs/showcase/acme-website.tsx +75 -39
- package/docs/showcase/futurelastic-web.tsx +91 -49
- package/docs/showcase/marketing-page.tsx +885 -0
- package/docs/showcase/table-footer-totals.tsx +12 -2
- package/docs/showcase/theme-customization.tsx +1259 -0
- package/package.json +5 -3
- package/scripts/brand-accent.generated.mjs +27 -0
- package/scripts/ui-audit.mjs +66 -0
- package/scripts/visual-audit-rules.mjs +46 -2
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Badge,
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
Descriptions,
|
|
11
|
+
ScrollArea,
|
|
12
|
+
} from "@godxjp/ui/data-display";
|
|
13
|
+
import { Button, Heading, Paragraph, Text } from "@godxjp/ui/general";
|
|
14
|
+
import { Affix, Flex, PageContainer } from "@godxjp/ui/layout";
|
|
15
|
+
import { Check, Filter } from "lucide-react";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Affix — Ant Design `Affix` (6.6.5). Pin an element to its scrollport, and REPORT that it is
|
|
19
|
+
* pinned — which is the whole difference from `position: sticky`, and the reason both website
|
|
20
|
+
* showcases in docs/showcase/ wrote `position: sticky` and got nothing usable out of it.
|
|
21
|
+
*
|
|
22
|
+
* The cases on this page are the ones that break a hand-rolled sticky header:
|
|
23
|
+
*
|
|
24
|
+
* — the PAGE JUMP. Taking a bar out of flow removes its height, so everything below it moves up by
|
|
25
|
+
* exactly that height. Card 1 prints the measured number it is holding open;
|
|
26
|
+
* — the CONDENSE. The bar is a different bar once pinned — a smaller control, a shorter title, a
|
|
27
|
+
* monogram instead of a wordmark — and nothing but the reported boolean can drive that;
|
|
28
|
+
* — a SCROLLING CONTAINER that is not the page, which is `target`;
|
|
29
|
+
* — the BLOCK-END pin, which is the sticky form footer and not the sticky header;
|
|
30
|
+
* — a bar of CJK beside Latin and a 71-character unbreakable token, because the measured height is
|
|
31
|
+
* only right if it is measured at the width it is actually laid out at;
|
|
32
|
+
* — and the narrow case, where the same bar wraps to two lines and the placeholder has to follow.
|
|
33
|
+
*
|
|
34
|
+
* Composed only from real @godxjp/ui components.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/** A real filename in a real toolbar: 71 characters with nowhere to break. */
|
|
38
|
+
const UNBREAKABLE = "RegionalDeploymentConfigurationParameterNamespaceIdentifierOverrides123";
|
|
39
|
+
|
|
40
|
+
function Filler({ id, title, lines }: { id: string; title: string; lines: number }) {
|
|
41
|
+
return (
|
|
42
|
+
<section id={id} aria-labelledby={`${id}-heading`}>
|
|
43
|
+
<Heading level={3} id={`${id}-heading`}>
|
|
44
|
+
{title}
|
|
45
|
+
</Heading>
|
|
46
|
+
{Array.from({ length: lines }, (_, line) => (
|
|
47
|
+
<Paragraph key={line}>
|
|
48
|
+
スクロールしてください。Scroll on, and keep scrolling: the bar above only becomes
|
|
49
|
+
interesting once the page has gone far enough past it for the pin to fire.
|
|
50
|
+
</Paragraph>
|
|
51
|
+
))}
|
|
52
|
+
</section>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default function Demo() {
|
|
57
|
+
const paneRef = useRef<HTMLDivElement>(null);
|
|
58
|
+
const [pinned, setPinned] = useState(false);
|
|
59
|
+
const [paneP, setPanePinned] = useState(false);
|
|
60
|
+
const [footerPinned, setFooterPinned] = useState(false);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<PageContainer
|
|
64
|
+
title="Affix"
|
|
65
|
+
subtitle="antd Affix · the measured placeholder, the reported boolean, the scrolling target"
|
|
66
|
+
>
|
|
67
|
+
<Flex direction="col" gap="lg">
|
|
68
|
+
<Card>
|
|
69
|
+
<CardHeader>
|
|
70
|
+
<CardTitle>1 · The condensing bar, against the page</CardTitle>
|
|
71
|
+
<CardDescription>
|
|
72
|
+
Everything that changes below is driven by `onChange` alone: the title loses its
|
|
73
|
+
qualifier, the primary action drops a size tier, and the state badge flips. Nothing
|
|
74
|
+
here is a second measurement, and `position: sticky` can drive none of it.
|
|
75
|
+
</CardDescription>
|
|
76
|
+
</CardHeader>
|
|
77
|
+
<CardContent>
|
|
78
|
+
<Affix offsetBlockStart={8} onChange={setPinned}>
|
|
79
|
+
<Card>
|
|
80
|
+
<CardContent>
|
|
81
|
+
<Flex align="center" justify="between" gap="sm" wrap>
|
|
82
|
+
<Text weight="medium">
|
|
83
|
+
{pinned ? "請求書 · Invoices" : "請求書 · Invoices, 2026 年度 (all regions)"}
|
|
84
|
+
</Text>
|
|
85
|
+
<Flex align="center" gap="sm">
|
|
86
|
+
<Badge variant={pinned ? "default" : "secondary"}>
|
|
87
|
+
{pinned ? "affixed" : "in flow"}
|
|
88
|
+
</Badge>
|
|
89
|
+
<Button size={pinned ? "sm" : "md"}>
|
|
90
|
+
<Check aria-hidden="true" />
|
|
91
|
+
承認 · Approve
|
|
92
|
+
</Button>
|
|
93
|
+
</Flex>
|
|
94
|
+
</Flex>
|
|
95
|
+
</CardContent>
|
|
96
|
+
</Card>
|
|
97
|
+
</Affix>
|
|
98
|
+
<Filler id="affix-page-body" title="A long page to scroll" lines={14} />
|
|
99
|
+
</CardContent>
|
|
100
|
+
</Card>
|
|
101
|
+
|
|
102
|
+
<Card>
|
|
103
|
+
<CardHeader>
|
|
104
|
+
<CardTitle>2 · Inside a scrolling target, not the page</CardTitle>
|
|
105
|
+
<CardDescription>
|
|
106
|
+
`target` is Ant Design's, and it is the answer to a page whose scroll belongs to
|
|
107
|
+
a pane: the bar pins against the pane's edge and stops there, not at the top of
|
|
108
|
+
the window. The label carries a 71-character unbreakable token beside CJK, so the
|
|
109
|
+
measured height is taken at the width the bar is really laid out at.
|
|
110
|
+
</CardDescription>
|
|
111
|
+
</CardHeader>
|
|
112
|
+
<CardContent>
|
|
113
|
+
<ScrollArea viewportRef={paneRef} className="h-80" label="Filter pane">
|
|
114
|
+
<Affix target={() => paneRef.current} onChange={setPanePinned}>
|
|
115
|
+
<Card>
|
|
116
|
+
<CardContent>
|
|
117
|
+
<Flex align="center" gap="sm" wrap>
|
|
118
|
+
<Filter aria-hidden="true" />
|
|
119
|
+
{/* `truncate`, not a wrap: a 71-character identifier with nowhere to break
|
|
120
|
+
would otherwise spill 255px past its box at 375px (measured by
|
|
121
|
+
check:frame-overflow, which is what caught it). A toolbar label is one
|
|
122
|
+
line; the full string stays in the DOM and in the accessible name. */}
|
|
123
|
+
<Text weight="medium" truncate className="min-w-0">
|
|
124
|
+
絞り込み · {UNBREAKABLE}
|
|
125
|
+
</Text>
|
|
126
|
+
<Badge variant={paneP ? "default" : "secondary"}>
|
|
127
|
+
{paneP ? "affixed" : "in flow"}
|
|
128
|
+
</Badge>
|
|
129
|
+
</Flex>
|
|
130
|
+
</CardContent>
|
|
131
|
+
</Card>
|
|
132
|
+
</Affix>
|
|
133
|
+
<Filler id="affix-pane-body" title="Pane content" lines={12} />
|
|
134
|
+
</ScrollArea>
|
|
135
|
+
</CardContent>
|
|
136
|
+
</Card>
|
|
137
|
+
|
|
138
|
+
<Card>
|
|
139
|
+
<CardHeader>
|
|
140
|
+
<CardTitle>3 · offsetBlockEnd · the sticky form footer</CardTitle>
|
|
141
|
+
<CardDescription>
|
|
142
|
+
Passing the block-END offset (and no block-start one) is what selects bottom pinning,
|
|
143
|
+
exactly as in Ant Design. It releases the moment the real end of the form reaches it,
|
|
144
|
+
which is the behaviour a `position: sticky` footer cannot express without a second
|
|
145
|
+
element.
|
|
146
|
+
</CardDescription>
|
|
147
|
+
</CardHeader>
|
|
148
|
+
<CardContent>
|
|
149
|
+
<Filler id="affix-form-body" title="Form fields" lines={10} />
|
|
150
|
+
<Affix offsetBlockEnd={8} onChange={setFooterPinned}>
|
|
151
|
+
<Card>
|
|
152
|
+
<CardContent>
|
|
153
|
+
<Flex align="center" justify="between" gap="sm" wrap>
|
|
154
|
+
<Text tone="muted">
|
|
155
|
+
{footerPinned ? "未保存の変更があります" : "すべて保存済み"}
|
|
156
|
+
</Text>
|
|
157
|
+
<Flex align="center" gap="sm">
|
|
158
|
+
<Badge variant={footerPinned ? "default" : "secondary"}>
|
|
159
|
+
{footerPinned ? "affixed" : "in flow"}
|
|
160
|
+
</Badge>
|
|
161
|
+
<Button variant="outline">キャンセル</Button>
|
|
162
|
+
<Button>保存</Button>
|
|
163
|
+
</Flex>
|
|
164
|
+
</Flex>
|
|
165
|
+
</CardContent>
|
|
166
|
+
</Card>
|
|
167
|
+
</Affix>
|
|
168
|
+
</CardContent>
|
|
169
|
+
</Card>
|
|
170
|
+
|
|
171
|
+
<Card>
|
|
172
|
+
<CardHeader>
|
|
173
|
+
<CardTitle>4 · What the component is actually reporting</CardTitle>
|
|
174
|
+
<CardDescription>
|
|
175
|
+
Three independent Affixes, three independent booleans. Each one fires on its own pin
|
|
176
|
+
transition and on nothing else, never once per scroll frame.
|
|
177
|
+
</CardDescription>
|
|
178
|
+
</CardHeader>
|
|
179
|
+
<CardContent>
|
|
180
|
+
<Descriptions
|
|
181
|
+
items={[
|
|
182
|
+
{
|
|
183
|
+
label: "1 · page bar",
|
|
184
|
+
children: (
|
|
185
|
+
<Badge variant={pinned ? "default" : "secondary"}>{String(pinned)}</Badge>
|
|
186
|
+
),
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
label: "2 · pane bar",
|
|
190
|
+
children: (
|
|
191
|
+
<Badge variant={paneP ? "default" : "secondary"}>{String(paneP)}</Badge>
|
|
192
|
+
),
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
label: "3 · form footer",
|
|
196
|
+
children: (
|
|
197
|
+
<Badge variant={footerPinned ? "default" : "secondary"}>
|
|
198
|
+
{String(footerPinned)}
|
|
199
|
+
</Badge>
|
|
200
|
+
),
|
|
201
|
+
},
|
|
202
|
+
]}
|
|
203
|
+
/>
|
|
204
|
+
</CardContent>
|
|
205
|
+
</Card>
|
|
206
|
+
</Flex>
|
|
207
|
+
</PageContainer>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Badge,
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
Thumbnail,
|
|
11
|
+
} from "@godxjp/ui/data-display";
|
|
12
|
+
import { Button, Text } from "@godxjp/ui/general";
|
|
13
|
+
import {
|
|
14
|
+
AppShell,
|
|
15
|
+
Flex,
|
|
16
|
+
Masonry,
|
|
17
|
+
PageContainer,
|
|
18
|
+
Sidebar,
|
|
19
|
+
type SidebarSectionProp,
|
|
20
|
+
Topbar,
|
|
21
|
+
type MasonryItemProp,
|
|
22
|
+
} from "@godxjp/ui/layout";
|
|
23
|
+
import { Bot, Images, Inbox, MessageSquare, Settings } from "lucide-react";
|
|
24
|
+
|
|
25
|
+
import coverTerrain from "../assets/cover-terrain.svg";
|
|
26
|
+
import shotPortrait from "../assets/shot-portrait.svg";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Masonry — Ant Design `Masonry` (6.0.0). A real inbox of mixed-height notes, not a tidy row of
|
|
30
|
+
* equal cards: the tidy demo is the one that hides every defect this layout actually has.
|
|
31
|
+
*
|
|
32
|
+
* Every card on this page is an EDGE:
|
|
33
|
+
* — a one-line note beside a twelve-line one, in the same run;
|
|
34
|
+
* — an IMAGE tile whose height is declared so the columns do not jump when it decodes;
|
|
35
|
+
* — a CJK tile of real Japanese prose at --line-height-body 1.7, which is taller per character
|
|
36
|
+
* than the Latin beside it;
|
|
37
|
+
* — the single-column case, which is what every narrow viewport collapses to;
|
|
38
|
+
* — a tile PINNED to the first column, which is where reading order and visual order part company;
|
|
39
|
+
* — a responsive `columns` map, driven live by the viewport.
|
|
40
|
+
*
|
|
41
|
+
* Composed only from real @godxjp/ui components.
|
|
42
|
+
*/
|
|
43
|
+
const sections: SidebarSectionProp[] = [
|
|
44
|
+
{
|
|
45
|
+
label: "受信",
|
|
46
|
+
items: [
|
|
47
|
+
{ id: "inbox", label: "メモ", icon: Inbox },
|
|
48
|
+
{ id: "gallery", label: "素材", icon: Images },
|
|
49
|
+
{ id: "chat", label: "やりとり", icon: MessageSquare },
|
|
50
|
+
{ id: "agents", label: "エージェント", icon: Bot },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{ label: "管理", items: [{ id: "settings", label: "設定", icon: Settings }] },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/** A one-line note. The shortest thing this layout ever has to place. */
|
|
57
|
+
function ShortNote({ title, body }: { title: string; body: string }) {
|
|
58
|
+
return (
|
|
59
|
+
<Card>
|
|
60
|
+
<CardHeader>
|
|
61
|
+
<CardTitle>{title}</CardTitle>
|
|
62
|
+
</CardHeader>
|
|
63
|
+
<CardContent>
|
|
64
|
+
<Text size="sm">{body}</Text>
|
|
65
|
+
</CardContent>
|
|
66
|
+
</Card>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const LONG_BODY = [
|
|
71
|
+
"The rule the team keeps re-deriving: a masonry has two orders and they do not agree.",
|
|
72
|
+
"DOM order is the order of `items`, always, because every tile is absolutely positioned and",
|
|
73
|
+
"nothing reorders the markup. Visual order is the packing: tile four lands under whichever of",
|
|
74
|
+
"the first three columns is shortest, so it can paint ABOVE a tile that precedes it in the",
|
|
75
|
+
"source. That is the form, and the consequence is one line long: order the items by importance,",
|
|
76
|
+
"never by height.",
|
|
77
|
+
"",
|
|
78
|
+
"It matters most for the keyboard. Tab visits this card before the short one beside it even",
|
|
79
|
+
"when this card's top edge sits lower on the screen, and a screen reader reads them in the same",
|
|
80
|
+
"sequence. The alternative implementation, CSS `column-count`, is worse on exactly that axis:",
|
|
81
|
+
"it fills the first column to the bottom before starting the second, so in a thirty-tile feed",
|
|
82
|
+
"the second tile in the DOM paints at the bottom-left corner of the viewport.",
|
|
83
|
+
].join(" ");
|
|
84
|
+
|
|
85
|
+
const JA_BODY =
|
|
86
|
+
"日本語の本文は 1 文字あたりの高さが欧文より大きい。全角の仮名と漢字が em ボックスを埋めるので、" +
|
|
87
|
+
"--line-height-body の 1.7 がそのまま行の高さに効く。同じ文字数でも、欧文の隣に置くと列の高さが" +
|
|
88
|
+
"先に伸びるのはそのためで、段組みの詰め方はこの差をそのまま受け取る。短い注記と長い注記を同じ列に" +
|
|
89
|
+
"並べたときに、どちらが先に読まれるかは見た目ではなく items の順序が決める。ここを取り違えると、" +
|
|
90
|
+
"画面では上にあるカードが、読み上げでは最後に来る。";
|
|
91
|
+
|
|
92
|
+
export default function MasonryDoc() {
|
|
93
|
+
const [columns, setColumns] = useState(3);
|
|
94
|
+
const [placed, setPlaced] = useState<number[]>([]);
|
|
95
|
+
|
|
96
|
+
const feed: MasonryItemProp[] = [
|
|
97
|
+
{
|
|
98
|
+
key: "one-line",
|
|
99
|
+
children: <ShortNote title="請求書 #4821" body="承認済み。" />,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
key: "image",
|
|
103
|
+
// The declared height is what keeps the columns still while the image decodes: without it
|
|
104
|
+
// every tile below this one jumps the moment the bytes land.
|
|
105
|
+
height: 268,
|
|
106
|
+
children: (
|
|
107
|
+
<Card>
|
|
108
|
+
<CardContent>
|
|
109
|
+
<Thumbnail src={coverTerrain} width={480} height={270} alt="" />
|
|
110
|
+
<Text size="xs" tone="muted">
|
|
111
|
+
cover-terrain.svg · 480×270
|
|
112
|
+
</Text>
|
|
113
|
+
</CardContent>
|
|
114
|
+
</Card>
|
|
115
|
+
),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
key: "long",
|
|
119
|
+
children: (
|
|
120
|
+
<Card>
|
|
121
|
+
<CardHeader>
|
|
122
|
+
<CardTitle>Reading order vs visual order</CardTitle>
|
|
123
|
+
<CardDescription>Twelve lines, beside a one-line card.</CardDescription>
|
|
124
|
+
</CardHeader>
|
|
125
|
+
<CardContent>
|
|
126
|
+
<Text size="sm">{LONG_BODY}</Text>
|
|
127
|
+
</CardContent>
|
|
128
|
+
</Card>
|
|
129
|
+
),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
key: "ja",
|
|
133
|
+
children: (
|
|
134
|
+
<Card>
|
|
135
|
+
<CardHeader>
|
|
136
|
+
<CardTitle>行の高さは言語で変わる</CardTitle>
|
|
137
|
+
</CardHeader>
|
|
138
|
+
<CardContent>
|
|
139
|
+
<Text size="sm">{JA_BODY}</Text>
|
|
140
|
+
</CardContent>
|
|
141
|
+
</Card>
|
|
142
|
+
),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
key: "badge",
|
|
146
|
+
children: (
|
|
147
|
+
<Card>
|
|
148
|
+
<CardContent>
|
|
149
|
+
<Flex gap="sm" wrap>
|
|
150
|
+
<Badge>下書き</Badge>
|
|
151
|
+
<Badge variant="secondary">保留</Badge>
|
|
152
|
+
<Badge variant="outline">期限切れ</Badge>
|
|
153
|
+
</Flex>
|
|
154
|
+
</CardContent>
|
|
155
|
+
</Card>
|
|
156
|
+
),
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
key: "pinned",
|
|
160
|
+
// PINNED. It stays fifth in the reading order whatever the packing does with it.
|
|
161
|
+
column: 0,
|
|
162
|
+
children: (
|
|
163
|
+
<Card>
|
|
164
|
+
<CardHeader>
|
|
165
|
+
<CardTitle>column: 0</CardTitle>
|
|
166
|
+
<CardDescription>
|
|
167
|
+
最初の列に固定。読み上げの順番は五番目のまま動かない。
|
|
168
|
+
</CardDescription>
|
|
169
|
+
</CardHeader>
|
|
170
|
+
</Card>
|
|
171
|
+
),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
key: "portrait",
|
|
175
|
+
height: 232,
|
|
176
|
+
children: (
|
|
177
|
+
<Card>
|
|
178
|
+
<CardContent>
|
|
179
|
+
<Thumbnail src={shotPortrait} width={360} height={640} alt="モバイル版の一覧画面" />
|
|
180
|
+
</CardContent>
|
|
181
|
+
</Card>
|
|
182
|
+
),
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
key: "two-line",
|
|
186
|
+
children: (
|
|
187
|
+
<ShortNote
|
|
188
|
+
title="経費精算"
|
|
189
|
+
body="領収書が二枚不足しています。差し戻し前に担当者へ確認してください。"
|
|
190
|
+
/>
|
|
191
|
+
),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
key: "shortest",
|
|
195
|
+
children: <ShortNote title="勤怠" body="—" />,
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<AppShell
|
|
201
|
+
sidebar={
|
|
202
|
+
<Sidebar
|
|
203
|
+
activeId="inbox"
|
|
204
|
+
sections={sections}
|
|
205
|
+
onSelect={() => {}}
|
|
206
|
+
product={{ name: "CoreDesk", role: "メモ", color: "hsl(var(--primary))" }}
|
|
207
|
+
/>
|
|
208
|
+
}
|
|
209
|
+
topbar={<Topbar />}
|
|
210
|
+
>
|
|
211
|
+
<PageContainer
|
|
212
|
+
title="段組みメモ"
|
|
213
|
+
subtitle="Masonry · 高さの揃わないカードを、そのつど一番低い列へ落としていく"
|
|
214
|
+
>
|
|
215
|
+
{/* THE REAL SCREEN. Responsive columns, a mixed feed, and the layout report. */}
|
|
216
|
+
<Flex direction="col" gap="sm">
|
|
217
|
+
<Flex gap="sm" align="center" wrap>
|
|
218
|
+
<Text size="sm" weight="medium">
|
|
219
|
+
columns · {columns} · fresh
|
|
220
|
+
</Text>
|
|
221
|
+
{[1, 2, 3, 4].map((count) => (
|
|
222
|
+
<Button
|
|
223
|
+
key={count}
|
|
224
|
+
size="sm"
|
|
225
|
+
variant={count === columns ? "default" : "outline"}
|
|
226
|
+
onClick={() => setColumns(count)}
|
|
227
|
+
>
|
|
228
|
+
{count}
|
|
229
|
+
</Button>
|
|
230
|
+
))}
|
|
231
|
+
<Text size="xs" tone="muted">
|
|
232
|
+
{placed.length === 0
|
|
233
|
+
? "onLayoutChange 待ち"
|
|
234
|
+
: `onLayoutChange · 列ごとの枚数 ${placed.join(" / ")}`}
|
|
235
|
+
</Text>
|
|
236
|
+
</Flex>
|
|
237
|
+
{/* `fresh` is ON here, and the reason is a measurement, not a preference: at 390px the
|
|
238
|
+
long English card re-wraps by one line AFTER the pass that measured it, and with the
|
|
239
|
+
container observer alone (antd's default) nothing notices — the container reported
|
|
240
|
+
2472.2px while its own content ended at 2489.7px. `fresh` observes each tile, so the
|
|
241
|
+
two agree. A feed with images and a live column count is exactly what it is for. */}
|
|
242
|
+
<Masonry
|
|
243
|
+
fresh
|
|
244
|
+
columns={columns}
|
|
245
|
+
gap="md"
|
|
246
|
+
items={feed}
|
|
247
|
+
onLayoutChange={(layout) => {
|
|
248
|
+
const perColumn = new Array<number>(columns).fill(0);
|
|
249
|
+
for (const entry of layout) perColumn[entry.column] += 1;
|
|
250
|
+
setPlaced(perColumn);
|
|
251
|
+
}}
|
|
252
|
+
/>
|
|
253
|
+
</Flex>
|
|
254
|
+
|
|
255
|
+
{/* THE NARROW CASE, on purpose and at full width: one column is what every phone gets,
|
|
256
|
+
and it is the arrangement a three-column demo never shows. */}
|
|
257
|
+
<Flex direction="col" gap="sm">
|
|
258
|
+
<Text size="sm" weight="medium">
|
|
259
|
+
単一列 · columns={1}
|
|
260
|
+
</Text>
|
|
261
|
+
<Text size="xs" tone="muted">
|
|
262
|
+
狭い画面はここに収束する。段組みが消えても読み上げの順番は同じ。DOM の順序は items
|
|
263
|
+
の順序のまま。
|
|
264
|
+
</Text>
|
|
265
|
+
<Masonry columns={1} gap="sm" items={feed.slice(0, 4)} />
|
|
266
|
+
</Flex>
|
|
267
|
+
|
|
268
|
+
{/* THE VIEWPORT MAP. `columns` takes the same step names Flex direction does. */}
|
|
269
|
+
<Flex direction="col" gap="sm">
|
|
270
|
+
<Text size="sm" weight="medium">
|
|
271
|
+
columns={"{ base: 1, sm: 2, md: 3, xl: 4 }"}
|
|
272
|
+
</Text>
|
|
273
|
+
<Text size="xs" tone="muted">
|
|
274
|
+
sm 40rem · md 48rem · lg 64rem · xl 80rem。Ant Design の `xs` はここでは `base`、`xxl`
|
|
275
|
+
はない。
|
|
276
|
+
</Text>
|
|
277
|
+
<Masonry columns={{ base: 1, sm: 2, md: 3, xl: 4 }} gap="md" items={feed.slice(2)} />
|
|
278
|
+
</Flex>
|
|
279
|
+
|
|
280
|
+
{/* NO GAP AT ALL — Ant Design's own default, which is what a token-less masonry looks
|
|
281
|
+
like. Worth seeing once so the `gap` step is a decision rather than a habit. */}
|
|
282
|
+
<Flex direction="col" gap="sm">
|
|
283
|
+
<Text size="sm" weight="medium">
|
|
284
|
+
gap 省略 · Ant Design の既定は 0
|
|
285
|
+
</Text>
|
|
286
|
+
<Masonry columns={3} items={feed.slice(0, 5)} />
|
|
287
|
+
</Flex>
|
|
288
|
+
</PageContainer>
|
|
289
|
+
</AppShell>
|
|
290
|
+
);
|
|
291
|
+
}
|