@godxjp/ui 30.3.1 → 30.4.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.
@@ -1,7 +1,7 @@
1
1
  /** Layout component prop types — @see docs/COMPONENTS.md#layout */
2
2
  import type * as React from "react";
3
3
  import type { ComponentType, ReactNode, SVGProps } from "react";
4
- import type { BreadcrumbProp, TitleProp, SubtitleProp, StatusProp, ExtraProp, FooterProp, PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, MobileShellHeightProp, MobileShellWidthProp, OrientationProp, TextAlignProp, TextToneProp, BreakpointProp, GapProp, PadProp, PadRawProp, ClassNameProp, ChildrenProp, IdProp, DisabledProp, DescriptionProp, ActionProp, IconProp, HeadingLevelProp, ToneProp, WidthProp, SizeProp } from "../vocabulary/index.js";
4
+ import type { BreadcrumbProp, TitleProp, SubtitleProp, StatusProp, ExtraProp, FooterProp, PageDensityProp, DensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, MobileShellHeightProp, MobileShellWidthProp, OrientationProp, TextAlignProp, TextToneProp, BreakpointProp, GapProp, PadProp, PadRawProp, ClassNameProp, ChildrenProp, IdProp, DisabledProp, DescriptionProp, ActionProp, IconProp, HeadingLevelProp, ToneProp, WidthProp, SizeProp } from "../vocabulary/index.js";
5
5
  import type { EmptyStateToneProp } from "./data-display.prop.js";
6
6
  /**
7
7
  * Arrangement of the page header's title band and its `extra` slot below the 640px step.
@@ -338,6 +338,53 @@ export type FlexProp = React.HTMLAttributes<HTMLElement> & {
338
338
  */
339
339
  measure?: FlexMeasureProp;
340
340
  };
341
+ /**
342
+ * @see SpaceCompact — antd `Space.Compact`, a row of controls welded into one visual unit: the
343
+ * inner corner radii are zeroed and the shared border is collapsed so two boxes read as one,
344
+ * while each child keeps its own focus ring and its own `size`.
345
+ *
346
+ * ## Deviations from `antd/es/space/Compact.d.ts` (read at `@ant-design/x` install time, not
347
+ * from memory — see the PR for the exact checkout), each written down per `DESIGN-AUTHORITY.md`
348
+ *
349
+ * - **No `Space`.** antd's `Space.Compact` is a static member of `Space`; this library has no
350
+ * `Space` at all, and does not gain one here — `Space`'s only other job (gaps between siblings)
351
+ * already lives on `Flex`/`ResponsiveGrid` `gap`, so a `Space` built only to host `.Compact`
352
+ * would duplicate that primitive for nothing (`docs/roadmap/parity-audit-layout-navigation-
353
+ * general.md` §4.2 marks bare `Space` a composition, not a gap). `SpaceCompact` ships standalone
354
+ * — the same flat-export shape as `CheckboxGroup`/`ToggleGroup`/`SearchSelect`/`TagInput`, not a
355
+ * `Space.Compact` dotted member.
356
+ * - **`size` → `density`, not forwarded.** antd's `size?: SizeType` (`small|middle|large`) is the
357
+ * `ConfigProvider` ambient-size cascade (`SpaceCompactItemContext`/`useCompactItemContext`,
358
+ * read by antd's own `Input`/`Select`/`Button`) — the exact capability this library already
359
+ * owns under `density` (`compact|default|comfortable`, `docs/DESIGN-AUTHORITY.md` "A capability
360
+ * this library already has keeps its own name"). `density` scopes `--scaling` to this row via
361
+ * `.ui-density-*` (the same class `Form.density`/`FormRoot.density` already emit), and each
362
+ * child keeps its OWN `size` (`SizeProp`) exactly as it does anywhere else — a second axis
363
+ * would be the duplicate spelling `check:prop-vocabulary` exists to prevent.
364
+ * - **`direction` not ported, only `orientation`.** The installed `SpaceCompactProps.direction` is
365
+ * itself `@deprecated please use \`orientation\` instead`; only the current, non-deprecated name
366
+ * is carried, reusing the vocabulary `OrientationProp` already shared by `Separator`/`Steps`/
367
+ * `RadioGroup`/`Toolbar` rather than a second `"horizontal" | "vertical"` spelling.
368
+ * - **`block` → `fullWidth`.** Same rename already applied to `Button.block`
369
+ * (`ButtonProp.fullWidth`) for the same reason — "this library's controlled vocabulary wins on
370
+ * values" — so one word means the same thing on both components.
371
+ * - **No `prefixCls`/`rootClassName`.** Plumbing for antd's `prefixCls`-rooted styling system,
372
+ * which this library does not have; every component here takes one `className`.
373
+ */
374
+ export type SpaceCompactProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
375
+ /** The controls to weld into one row — antd `Space.Compact` children. */
376
+ children?: ChildrenProp;
377
+ /** Layout axis. Default `horizontal` (antd `orientation`, `direction` deprecated upstream). */
378
+ orientation?: OrientationProp;
379
+ /** antd's boolean spelling of `orientation="vertical"`. `orientation` wins when both are set. */
380
+ vertical?: boolean;
381
+ /** Row fills its parent's inline size (antd `block`, renamed to match `Button.fullWidth`). */
382
+ fullWidth?: boolean;
383
+ /** Scoped control density for the whole row (antd `Space.Compact` `size`). */
384
+ density?: DensityProp;
385
+ id?: IdProp;
386
+ className?: ClassNameProp;
387
+ };
341
388
  /** Container column counts; omitted steps inherit from the previous step. Base defaults to 1. */
342
389
  export type ResponsiveGridColumnsProp = number | {
343
390
  base?: number;
@@ -837,6 +837,19 @@ export declare const COMPONENT_PROP_REGISTRY: {
837
837
  readonly reason: 'The CENTRED, CAPPED column (narrow | medium | wide), backed by --page-measure-* — the inner half of "full-bleed outside, measured column inside" (gh#839). Same word and same three token steps as PageContainer `measure`, on the primitive a full-bleed <section> can actually use: PageContainer owns page padding and a header scaffold, which is why measure="wide" was refused there, and the refusal left the centred column with no owner at all — three showcases hand-wrote the same four declarations. It adds NO gutter (that is `pad`), and an explicit `width` wins over it.';
838
838
  }];
839
839
  };
840
+ readonly SpaceCompactProp: {
841
+ readonly group: "layout";
842
+ readonly file: "components/layout.prop.ts";
843
+ readonly vocabulary: readonly ["OrientationProp", {
844
+ readonly field: "vertical";
845
+ readonly local: true;
846
+ readonly reason: "antd's boolean spelling of orientation=\"vertical\"; orientation wins over it.";
847
+ }, {
848
+ readonly field: "fullWidth";
849
+ readonly local: true;
850
+ readonly reason: "antd Space.Compact `block`, renamed to match the existing Button.block → fullWidth rename (same axis, same word everywhere it appears).";
851
+ }, "DensityProp", "IdProp", "ClassNameProp"];
852
+ };
840
853
  readonly ResponsiveGridFlowProp: {
841
854
  readonly group: "layout";
842
855
  readonly file: "components/layout.prop.ts";
@@ -3538,6 +3551,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
3538
3551
  readonly file: "components/data-entry.prop.ts";
3539
3552
  readonly vocabulary: readonly [];
3540
3553
  };
3554
+ readonly ChatComposerFooterProp: {
3555
+ readonly group: "data-entry";
3556
+ readonly file: "components/data-entry.prop.ts";
3557
+ readonly vocabulary: readonly [];
3558
+ };
3541
3559
  readonly ChatComposerProp: {
3542
3560
  readonly group: "data-entry";
3543
3561
  readonly file: "components/data-entry.prop.ts";
@@ -838,6 +838,26 @@ const COMPONENT_PROP_REGISTRY = {
838
838
  }
839
839
  ]
840
840
  },
841
+ SpaceCompactProp: {
842
+ group: "layout",
843
+ file: "components/layout.prop.ts",
844
+ vocabulary: [
845
+ "OrientationProp",
846
+ {
847
+ field: "vertical",
848
+ local: true,
849
+ reason: `antd's boolean spelling of orientation="vertical"; orientation wins over it.`
850
+ },
851
+ {
852
+ field: "fullWidth",
853
+ local: true,
854
+ reason: "antd Space.Compact `block`, renamed to match the existing Button.block \u2192 fullWidth rename (same axis, same word everywhere it appears)."
855
+ },
856
+ "DensityProp",
857
+ "IdProp",
858
+ "ClassNameProp"
859
+ ]
860
+ },
841
861
  ResponsiveGridFlowProp: { group: "layout", file: "components/layout.prop.ts", vocabulary: [] },
842
862
  ResponsiveGridColumnsProp: { group: "layout", file: "components/layout.prop.ts", vocabulary: [] },
843
863
  ResponsiveGridPresetProp: { group: "layout", file: "components/layout.prop.ts", vocabulary: [] },
@@ -4226,6 +4246,11 @@ const COMPONENT_PROP_REGISTRY = {
4226
4246
  file: "components/data-entry.prop.ts",
4227
4247
  vocabulary: []
4228
4248
  },
4249
+ ChatComposerFooterProp: {
4250
+ group: "data-entry",
4251
+ file: "components/data-entry.prop.ts",
4252
+ vocabulary: []
4253
+ },
4229
4254
  ChatComposerProp: {
4230
4255
  group: "data-entry",
4231
4256
  file: "components/data-entry.prop.ts",
@@ -123,6 +123,57 @@
123
123
  flex-direction: column;
124
124
  }
125
125
 
126
+ .ui-space-compact {
127
+ display: inline-flex;
128
+ align-items: stretch;
129
+ }
130
+
131
+ .ui-space-compact[data-full-width="true"] {
132
+ display: flex;
133
+ inline-size: 100%;
134
+ }
135
+
136
+ .ui-space-compact[data-full-width="true"] > * {
137
+ flex: 1 1 0%;
138
+ min-inline-size: 0;
139
+ }
140
+
141
+ .ui-space-compact[data-orientation="vertical"] {
142
+ flex-direction: column;
143
+ }
144
+
145
+ .ui-space-compact[data-orientation="vertical"][data-full-width="true"] > * {
146
+ min-inline-size: auto;
147
+ min-block-size: 0;
148
+ }
149
+
150
+ .ui-space-compact[data-orientation="horizontal"] > * + * {
151
+ margin-inline-start: calc(var(--control-border-width, var(--stroke-hairline)) * -1);
152
+ }
153
+
154
+ .ui-space-compact[data-orientation="vertical"] > * + * {
155
+ margin-block-start: calc(var(--control-border-width, var(--stroke-hairline)) * -1);
156
+ }
157
+
158
+ .ui-space-compact > * {
159
+ position: relative;
160
+ }
161
+
162
+ .ui-space-compact > *:hover,
163
+ .ui-space-compact > *:focus-within {
164
+ z-index: 1;
165
+ }
166
+
167
+ .ui-space-compact[data-orientation="horizontal"] > *:not(:first-child) {
168
+ --input-radius-start: 0;
169
+ --control-trigger-radius-start: 0;
170
+ }
171
+
172
+ .ui-space-compact[data-orientation="horizontal"] > *:not(:last-child) {
173
+ --input-radius-end: 0;
174
+ --control-trigger-radius-end: 0;
175
+ }
176
+
126
177
  .ui-empty-state[data-variant="section"] {
127
178
  padding: var(--empty-state-section-space-y) var(--empty-state-section-space-x);
128
179
  }
@@ -3,8 +3,8 @@
3
3
  > Generated by `scripts/frame-coverage.mjs` (issue #163). Do not edit by hand — run `pnpm check:frame-coverage`.
4
4
  > Standard: [FRAME-COVERAGE-STANDARD.md](./FRAME-COVERAGE-STANDARD.md). A missing dimension is **UNTESTED**, never a pass.
5
5
 
6
- - Public components: **171**
7
- - With ≥1 frame: **150** · zero-frame: **21**
6
+ - Public components: **172**
7
+ - With ≥1 frame: **151** · zero-frame: **21**
8
8
  - Fully covered (every contract axis declared): **0**
9
9
 
10
10
  ## UNTESTED — no `/frame/**` route
@@ -48,6 +48,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
48
48
  | RangeTimeline | **✗** | · | N/A | N/A | · | · | · | · | · | · |
49
49
  | PageContainer | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
50
50
  | Flex | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
51
+ | SpaceCompact | ✓ | · | N/A | N/A | · | · | · | · | · | · |
51
52
  | ResponsiveGrid | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
52
53
  | AppShell | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
53
54
  | AuthShell | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
@@ -168,6 +168,16 @@ export default function Demo() {
168
168
  const [savedStatus, setSavedStatus] = useState("open");
169
169
  const [comments, setComments] = useState<Comment[]>([]);
170
170
 
171
+ // ── Card 2c: footer as a NodeRender — the toolbar-below layout ─────────────────────────────
172
+ const [toolbarDraft, setToolbarDraft] = useState("");
173
+ const [toolbarComments, setToolbarComments] = useState<string[]>([]);
174
+
175
+ function postToolbarComment(text: string) {
176
+ if (!text) return;
177
+ setToolbarComments((current) => [...current, text]);
178
+ setToolbarDraft("");
179
+ }
180
+
171
181
  function postComment(text: string) {
172
182
  const changed = status !== savedStatus;
173
183
  // An empty draft with no status change carries nothing — the consumer decides, not the box.
@@ -263,13 +273,17 @@ export default function Demo() {
263
273
  onCancel={cancel}
264
274
  placeholder="メッセージを入力"
265
275
  prefix={
266
- <Button size="icon-sm" variant="ghost" aria-label="ファイルを添付">
276
+ <Button
277
+ size="icon-sm"
278
+ variant="ghost"
279
+ aria-label={t("chatComposerDocs.attach")}
280
+ >
267
281
  <Paperclip aria-hidden="true" />
268
282
  </Button>
269
283
  }
270
284
  footer={
271
285
  <Text size="xs" tone="muted">
272
- Enter で送信 · Shift + Enter で改行
286
+ {t("chatComposerDocs.enterHint")}
273
287
  </Text>
274
288
  }
275
289
  />
@@ -301,7 +315,7 @@ export default function Demo() {
301
315
  }}
302
316
  footer={
303
317
  <Text size="xs" tone="muted">
304
- Enter で送信 · Shift + Enter で改行
318
+ {t("chatComposerDocs.enterHint")}
305
319
  </Text>
306
320
  }
307
321
  />
@@ -340,7 +354,7 @@ export default function Demo() {
340
354
  <Flex direction="col" gap="md">
341
355
  {comments.length === 0 ? (
342
356
  <Text size="sm" tone="muted">
343
- まだコメントはありません
357
+ {t("chatComposerDocs.noComments")}
344
358
  </Text>
345
359
  ) : (
346
360
  comments.map((comment) => (
@@ -362,7 +376,7 @@ export default function Demo() {
362
376
  value={commentDraft}
363
377
  onValueChange={setCommentDraft}
364
378
  onSubmit={postComment}
365
- placeholder="コメントを入力"
379
+ placeholder={t("chatComposerDocs.commentPlaceholder")}
366
380
  submitLabel="コメントを投稿"
367
381
  footer={
368
382
  <Flex direction="row" gap="sm" align="center" justify="between" wrap>
@@ -385,6 +399,48 @@ export default function Demo() {
385
399
  </CardContent>
386
400
  </Card>
387
401
 
402
+ {/* ── 2c. footer as NodeRender — toolbar BELOW a full-width draft box ─────────────── */}
403
+ <Card>
404
+ <CardHeader>
405
+ <CardTitle level={2}>{t("chatComposerDocs.footerTitle")}</CardTitle>
406
+ <CardDescription>{t("chatComposerDocs.footerBody")}</CardDescription>
407
+ </CardHeader>
408
+ <CardContent>
409
+ <Flex direction="col" gap="md">
410
+ {toolbarComments.length === 0 ? (
411
+ <Text size="sm" tone="muted">
412
+ {t("chatComposerDocs.noComments")}
413
+ </Text>
414
+ ) : (
415
+ toolbarComments.map((body, index) => <Text key={index}>{body}</Text>)
416
+ )}
417
+ <ChatComposer
418
+ aria-label={t("chatComposerDocs.commentLabel")}
419
+ value={toolbarDraft}
420
+ onValueChange={setToolbarDraft}
421
+ onSubmit={postToolbarComment}
422
+ placeholder={t("chatComposerDocs.commentPlaceholder")}
423
+ actions={false}
424
+ footer={({ components: { SubmitButton } }) => (
425
+ <Flex direction="row" gap="sm" align="center" justify="between">
426
+ <Button
427
+ size="icon-sm"
428
+ variant="ghost"
429
+ aria-label={t("chatComposerDocs.attach")}
430
+ >
431
+ <Paperclip aria-hidden="true" />
432
+ </Button>
433
+ <Text size="xs" tone="muted">
434
+ {t("chatComposerDocs.enterHint")}
435
+ </Text>
436
+ <SubmitButton aria-label={t("chatComposerDocs.sendComment")} />
437
+ </Flex>
438
+ )}
439
+ />
440
+ </Flex>
441
+ </CardContent>
442
+ </Card>
443
+
388
444
  {/* ── 3. Every size step ───────────────────────────────────────────────────────── */}
389
445
  <Card>
390
446
  <CardHeader>
@@ -440,7 +496,7 @@ export default function Demo() {
440
496
  </Flex>
441
497
  }
442
498
  prefix={
443
- <Button size="icon-sm" variant="ghost" aria-label="ファイルを添付">
499
+ <Button size="icon-sm" variant="ghost" aria-label={t("chatComposerDocs.attach")}>
444
500
  <Paperclip aria-hidden="true" />
445
501
  </Button>
446
502
  }
@@ -1201,5 +1201,64 @@
1201
1201
  "recovery": "Store these recovery codes somewhere safe. They are shown once.",
1202
1202
  "acknowledge": "I have saved them"
1203
1203
  }
1204
+ },
1205
+ "spaceCompactDocs": {
1206
+ "title": "SpaceCompact",
1207
+ "subtitle": "Read a row of controls as ONE box — recurrence, search, an amount with its unit",
1208
+ "recur": {
1209
+ "title": "Recurrence (every N weeks)",
1210
+ "body": "The number and the unit are one setting, so they read as one box. FormField lands the label on the ROW, so a screen reader opens with “Recurrence, group” and each control inside keeps its own name.",
1211
+ "label": "Recurrence",
1212
+ "helper": "How often the invoice is issued automatically.",
1213
+ "every": "Interval",
1214
+ "unit": "Unit",
1215
+ "reads": "Reads as",
1216
+ "role": "role",
1217
+ "roleValue": "group (only on a NAMED row)"
1218
+ },
1219
+ "unit": {
1220
+ "day": "day",
1221
+ "week": "week",
1222
+ "month": "month"
1223
+ },
1224
+ "search": {
1225
+ "title": "Search and its button",
1226
+ "body": "Typing and running are one continuous action, so the seam goes. The button is still a real Button — focus ring and disabled keep working.",
1227
+ "row": "Search employees",
1228
+ "term": "Search term",
1229
+ "run": "Run the search",
1230
+ "placeholder": "Name or employee number"
1231
+ },
1232
+ "amount": {
1233
+ "title": "Amount, currency, and fullWidth",
1234
+ "body": "`fullWidth` stretches the row to its parent (antd `block`). The proportions inside stay each control's own, so the digits do not crush on a narrow screen.",
1235
+ "label": "Invoice amount",
1236
+ "value": "Amount",
1237
+ "currency": "Currency"
1238
+ },
1239
+ "vertical": {
1240
+ "title": "orientation=\"vertical\" — a written-down partial port",
1241
+ "body": "A vertical stack collapses the shared border, but corner-zeroing is inline-only for now: the Input and trigger corner tokens exist on the inline axis only. Written down as a follow-up rather than left as a silent gap.",
1242
+ "row": "Note and send",
1243
+ "note": "Handover note",
1244
+ "send": "Send",
1245
+ "alias": "`vertical` is antd's boolean spelling. Set both and `orientation` wins."
1246
+ },
1247
+ "dont": {
1248
+ "title": "When NOT to weld",
1249
+ "body": "Joining things that are not one setting makes unrelated values look like one box. Keep them in separate FormFields.",
1250
+ "dept": "Department code",
1251
+ "ext": "Extension"
1252
+ }
1253
+ },
1254
+ "chatComposerDocs": {
1255
+ "footerTitle": "footer as NodeRender · move attach / hint / send into a bottom toolbar",
1256
+ "footerBody": "With `actions={false}` the inline send button is hidden, and passing a function to `footer` hands you `components.SubmitButton` — already wired to onSubmit, disabled and the loading swap. The draft box keeps its full width; attach sits at the row start and send at the end.",
1257
+ "noComments": "No comments yet",
1258
+ "commentLabel": "Comment (bottom toolbar)",
1259
+ "commentPlaceholder": "Write a comment",
1260
+ "attach": "Attach a file",
1261
+ "enterHint": "Enter to send · Shift + Enter for a new line",
1262
+ "sendComment": "Send the comment"
1204
1263
  }
1205
1264
  }
@@ -1195,5 +1195,64 @@
1195
1195
  "recovery": "リカバリーコードは安全な場所に保管してください。表示は一度きりです。",
1196
1196
  "acknowledge": "保管しました"
1197
1197
  }
1198
+ },
1199
+ "spaceCompactDocs": {
1200
+ "title": "SpaceCompact",
1201
+ "subtitle": "くっつけた1つの箱として読ませる — 繰り返し設定・検索・単位つき金額",
1202
+ "recur": {
1203
+ "title": "繰り返し(毎 N 週ごと)",
1204
+ "body": "数値と単位はひとつの設定なので、ひとつの箱に見せます。ラベルは FormField が行に付けるので、読み上げは「繰り返し、グループ」から始まり、中のコントロールはそれぞれの名前を保ちます。",
1205
+ "label": "繰り返し",
1206
+ "helper": "請求書を自動発行する間隔です。",
1207
+ "every": "間隔",
1208
+ "unit": "単位",
1209
+ "reads": "読み方",
1210
+ "role": "role",
1211
+ "roleValue": "group(名前が付いた行のみ)"
1212
+ },
1213
+ "unit": {
1214
+ "day": "日",
1215
+ "week": "週",
1216
+ "month": "月"
1217
+ },
1218
+ "search": {
1219
+ "title": "検索とボタン",
1220
+ "body": "入力と実行は一続きの操作なので継ぎ目をなくします。ボタンは本物の Button のままなので、フォーカスリングも disabled もそのまま効きます。",
1221
+ "row": "社員を検索",
1222
+ "term": "検索語",
1223
+ "run": "検索する",
1224
+ "placeholder": "氏名・社員番号"
1225
+ },
1226
+ "amount": {
1227
+ "title": "金額と通貨、そして fullWidth",
1228
+ "body": "`fullWidth` は行を親の幅いっぱいに広げます(antd の `block`)。中の比率は各コントロールのままなので、狭い画面でも桁が潰れません。",
1229
+ "label": "請求金額",
1230
+ "value": "金額",
1231
+ "currency": "通貨"
1232
+ },
1233
+ "vertical": {
1234
+ "title": "orientation=\"vertical\" — 書かれた上での部分対応",
1235
+ "body": "縦積みは共有の境界線を潰しますが、角の丸めは今のところ横方向だけです。Input とトリガーの角トークンが inline 方向にしか無いためで、黙って欠けているのではなくフォローアップとして書いてあります。",
1236
+ "row": "メモと送信",
1237
+ "note": "申し送り事項",
1238
+ "send": "送信",
1239
+ "alias": "`vertical` は antd 互換の真偽値スペルです。`orientation` と両方指定すると `orientation` が勝ちます。"
1240
+ },
1241
+ "dont": {
1242
+ "title": "くっつけない方がよい場合",
1243
+ "body": "ひとつの設定ではないものを繋ぐと、関係のない値が同じ箱に見えます。別々の FormField に分けてください。",
1244
+ "dept": "部署コード",
1245
+ "ext": "内線"
1246
+ }
1247
+ },
1248
+ "chatComposerDocs": {
1249
+ "footerTitle": "footer as NodeRender · attach / hint / send を下段のツールバーへ",
1250
+ "footerBody": "actions={false} で行内の送信ボタンを隠し、footer に関数を渡すと components.SubmitButton(onSubmit / disabled / ローディング切替が配線済み)を受け取れます。下書き欄は全幅のまま、添付は行頭・送信は行末に配置できます。",
1251
+ "noComments": "まだコメントはありません",
1252
+ "commentLabel": "コメント(下段ツールバー)",
1253
+ "commentPlaceholder": "コメントを入力",
1254
+ "attach": "ファイルを添付",
1255
+ "enterHint": "Enter で送信 · Shift + Enter で改行",
1256
+ "sendComment": "コメントを送信"
1198
1257
  }
1199
1258
  }
@@ -1195,5 +1195,64 @@
1195
1195
  "recovery": "Hãy cất các mã khôi phục ở nơi an toàn. Chúng chỉ hiện một lần.",
1196
1196
  "acknowledge": "Tôi đã lưu lại"
1197
1197
  }
1198
+ },
1199
+ "spaceCompactDocs": {
1200
+ "title": "SpaceCompact",
1201
+ "subtitle": "Đọc một hàng control như MỘT hộp — lặp lại, tìm kiếm, số tiền kèm đơn vị",
1202
+ "recur": {
1203
+ "title": "Lặp lại (mỗi N tuần)",
1204
+ "body": "Con số và đơn vị là một thiết lập, nên chúng đọc như một hộp. FormField gắn nhãn lên HÀNG, nên screen reader mở đầu bằng “Lặp lại, nhóm” và mỗi control bên trong vẫn giữ tên riêng.",
1205
+ "label": "Lặp lại",
1206
+ "helper": "Tần suất phát hành hoá đơn tự động.",
1207
+ "every": "Khoảng",
1208
+ "unit": "Đơn vị",
1209
+ "reads": "Đọc thành",
1210
+ "role": "role",
1211
+ "roleValue": "group (chỉ khi hàng CÓ tên)"
1212
+ },
1213
+ "unit": {
1214
+ "day": "ngày",
1215
+ "week": "tuần",
1216
+ "month": "tháng"
1217
+ },
1218
+ "search": {
1219
+ "title": "Tìm kiếm và nút của nó",
1220
+ "body": "Gõ và chạy là một thao tác liền mạch nên bỏ đường nối. Nút vẫn là Button thật — vòng focus và disabled vẫn hoạt động.",
1221
+ "row": "Tìm nhân viên",
1222
+ "term": "Từ khoá",
1223
+ "run": "Chạy tìm kiếm",
1224
+ "placeholder": "Tên hoặc mã nhân viên"
1225
+ },
1226
+ "amount": {
1227
+ "title": "Số tiền, tiền tệ và fullWidth",
1228
+ "body": "`fullWidth` kéo hàng đầy chiều rộng cha (antd `block`). Tỉ lệ bên trong vẫn của từng control nên chữ số không bị bóp trên màn hẹp.",
1229
+ "label": "Số tiền hoá đơn",
1230
+ "value": "Số tiền",
1231
+ "currency": "Tiền tệ"
1232
+ },
1233
+ "vertical": {
1234
+ "title": "orientation=\"vertical\" — phần port còn thiếu, đã ghi rõ",
1235
+ "body": "Xếp dọc thì gộp được đường viền chung, nhưng bo góc hiện chỉ làm ở trục ngang: token góc của Input và trigger chỉ có ở trục inline. Ghi rõ thành việc tiếp theo, không để thiếu âm thầm.",
1236
+ "row": "Ghi chú và gửi",
1237
+ "note": "Ghi chú bàn giao",
1238
+ "send": "Gửi",
1239
+ "alias": "`vertical` là cách viết boolean của antd. Đặt cả hai thì `orientation` thắng."
1240
+ },
1241
+ "dont": {
1242
+ "title": "Khi KHÔNG nên nối",
1243
+ "body": "Nối những thứ không phải một thiết lập sẽ khiến các giá trị không liên quan trông như một hộp. Hãy tách FormField riêng.",
1244
+ "dept": "Mã phòng ban",
1245
+ "ext": "Số máy lẻ"
1246
+ }
1247
+ },
1248
+ "chatComposerDocs": {
1249
+ "footerTitle": "footer dạng NodeRender · đưa đính kèm / gợi ý / gửi xuống thanh dưới",
1250
+ "footerBody": "Với `actions={false}` nút gửi nội dòng bị ẩn, và truyền một hàm vào `footer` sẽ trả về `components.SubmitButton` — đã nối sẵn onSubmit, disabled và trạng thái loading. Ô soạn giữ nguyên toàn chiều rộng; đính kèm ở đầu hàng, gửi ở cuối.",
1251
+ "noComments": "Chưa có bình luận nào",
1252
+ "commentLabel": "Bình luận (thanh dưới)",
1253
+ "commentPlaceholder": "Nhập bình luận",
1254
+ "attach": "Đính kèm tệp",
1255
+ "enterHint": "Enter để gửi · Shift + Enter để xuống dòng",
1256
+ "sendComment": "Gửi bình luận"
1198
1257
  }
1199
1258
  }