@godxjp/ui 31.29.0 → 31.30.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.
@@ -353,6 +353,10 @@
353
353
  border-radius: var(--legend-swatch-radius);
354
354
  }
355
355
 
356
+ .ui-legend-swatch[data-color="true"] {
357
+ background: var(--legend-swatch-color, hsl(var(--muted-foreground)));
358
+ }
359
+
356
360
  .ui-legend-swatch[data-tone="default"] {
357
361
  background: hsl(
358
362
  var(
@@ -2008,6 +2012,50 @@
2008
2012
  .ui-range-timeline-header {
2009
2013
  background: hsl(var(--muted));
2010
2014
  }
2015
+
2016
+ .ui-range-timeline-label-resizer {
2017
+ position: absolute;
2018
+ inset-block: 0;
2019
+ inset-inline-end: calc(var(--range-timeline-resizer-inline-size) / -2);
2020
+ z-index: 2;
2021
+ inline-size: var(--range-timeline-resizer-inline-size);
2022
+ cursor: col-resize;
2023
+ touch-action: none;
2024
+ }
2025
+ .ui-range-timeline-label-resizer::after {
2026
+ content: "";
2027
+ position: absolute;
2028
+ inset-block: 0;
2029
+ inset-inline-start: 50%;
2030
+ inline-size: var(--range-timeline-resizer-active-width);
2031
+ translate: -50% 0;
2032
+ background: transparent;
2033
+ }
2034
+ .ui-range-timeline-label-resizer:hover::after,
2035
+ .ui-range-timeline-label-resizer:focus-visible::after {
2036
+ background: hsl(var(--primary));
2037
+ }
2038
+ .ui-range-timeline-label-resizer:focus-visible {
2039
+ outline: none;
2040
+ }
2041
+
2042
+ .ui-range-timeline[data-busy="true"] .ui-range-timeline-body {
2043
+ opacity: var(--range-timeline-busy-body-alpha);
2044
+ }
2045
+ .ui-range-timeline-busy {
2046
+ position: relative;
2047
+ overflow: hidden;
2048
+ block-size: var(--range-timeline-busy-line-block-size);
2049
+ background: hsl(var(--primary) / 0.2);
2050
+ }
2051
+ .ui-range-timeline-busy::before {
2052
+ content: "";
2053
+ position: absolute;
2054
+ inset-block: 0;
2055
+ inline-size: 30%;
2056
+ background: hsl(var(--primary));
2057
+ animation: ui-range-timeline-busy var(--duration-slow, 1.2s) ease-in-out infinite;
2058
+ }
2011
2059
  .ui-range-timeline-label {
2012
2060
  position: sticky;
2013
2061
  inset-inline-start: 0;
@@ -2109,10 +2157,77 @@
2109
2157
  --radius-sm,
2110
2158
  calc(var(--radius) / var(--radius-ratio) / var(--radius-ratio))
2111
2159
  );
2112
- background: hsl(var(--primary));
2113
- color: hsl(var(--primary-foreground));
2160
+ background: var(--range-timeline-bar-color, hsl(var(--primary)));
2161
+ color: var(--range-timeline-bar-ink, hsl(var(--primary-foreground)));
2162
+ }
2163
+
2164
+ .ui-range-timeline-plan,
2165
+ .ui-range-timeline-overrun,
2166
+ .ui-range-timeline-early {
2167
+ position: absolute;
2168
+ inset-block-start: 50%;
2169
+ transform: translateY(-50%);
2170
+ block-size: var(--range-timeline-bar-height);
2171
+ }
2172
+ .ui-range-timeline-plan {
2173
+ border: var(--range-timeline-plan-border-width) dashed hsl(var(--foreground) / 0.45);
2174
+ border-radius: var(
2175
+ --radius-sm,
2176
+ calc(var(--radius) / var(--radius-ratio) / var(--radius-ratio))
2177
+ );
2178
+ }
2179
+ .ui-range-timeline-overrun {
2180
+ z-index: 1;
2181
+ display: flex;
2182
+ align-items: center;
2183
+ justify-content: flex-end;
2184
+ overflow: visible;
2185
+ background: hsl(var(--destructive));
2186
+ color: hsl(var(--destructive-foreground));
2187
+ pointer-events: none;
2188
+ }
2189
+ .ui-range-timeline-variance {
2190
+ position: absolute;
2191
+ inset-inline-start: 100%;
2192
+ padding-inline: var(--space-1);
2193
+ color: hsl(var(--text-error));
2194
+ font-size: var(--font-size-xs, calc(var(--font-size-base) / var(--font-size-ratio)));
2195
+ font-weight: var(--font-weight-medium);
2196
+ white-space: nowrap;
2197
+ }
2198
+ .ui-range-timeline-plan-note {
2199
+ position: absolute;
2200
+ inset-block-start: 50%;
2201
+ transform: translateY(-50%);
2202
+ padding-inline: var(--space-1);
2203
+ color: hsl(var(--muted-foreground));
2204
+ font-size: var(--font-size-xs, calc(var(--font-size-base) / var(--font-size-ratio)));
2205
+ white-space: nowrap;
2206
+ pointer-events: none;
2207
+ }
2208
+ .ui-range-timeline-early {
2209
+ z-index: 1;
2210
+ inline-size: var(--range-timeline-early-marker-inline-size);
2211
+ background: hsl(var(--success));
2212
+ pointer-events: none;
2213
+ }
2214
+ .ui-range-timeline-bar[data-muted="true"] {
2215
+ opacity: var(--range-timeline-bar-muted-alpha);
2216
+ }
2217
+ .ui-range-timeline-bar[data-overdue="true"] {
2218
+ box-shadow: inset calc(-1 * var(--range-timeline-bar-overdue-width)) 0 0 hsl(var(--destructive));
2219
+ }
2220
+ [dir="rtl"] .ui-range-timeline-bar[data-overdue="true"] {
2221
+ box-shadow: inset var(--range-timeline-bar-overdue-width) 0 0 hsl(var(--destructive));
2222
+ }
2223
+
2224
+ .ui-range-timeline-bar-hit {
2225
+ position: absolute;
2226
+ inset: 0;
2227
+ border-radius: inherit;
2114
2228
  }
2115
2229
  .ui-range-timeline-handle {
2230
+ z-index: 1;
2116
2231
  position: absolute;
2117
2232
  inset-block: 0;
2118
2233
  min-inline-size: 0;
@@ -3372,3 +3487,21 @@
3372
3487
  display: contents;
3373
3488
  }
3374
3489
  }
3490
+
3491
+ @keyframes ui-range-timeline-busy {
3492
+ from {
3493
+ inset-inline-start: -30%;
3494
+ }
3495
+ to {
3496
+ inset-inline-start: 100%;
3497
+ }
3498
+ }
3499
+
3500
+ @media (prefers-reduced-motion: reduce) {
3501
+ .ui-range-timeline-busy::before {
3502
+ animation: none;
3503
+ inset-inline-start: 0;
3504
+ inline-size: 100%;
3505
+ opacity: 0.6;
3506
+ }
3507
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "31.29.0",
2
+ "version": "31.30.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",
@@ -250,6 +250,12 @@
250
250
  :root {
251
251
  --range-timeline-label-width: var(--app-shell-sidebar-width);
252
252
 
253
+ --range-timeline-resizer-inline-size: var(--space-6);
254
+ --range-timeline-resizer-active-width: var(--stroke-md, 2px);
255
+
256
+ --range-timeline-busy-body-alpha: 0.55;
257
+ --range-timeline-busy-line-block-size: var(--stroke-md, 2px);
258
+
253
259
  --range-timeline-unit-width: var(--range-timeline-unit-width-default);
254
260
 
255
261
  --range-timeline-unit-width-compact: calc(var(--control-height-sm) * 1.5);
@@ -258,6 +264,12 @@
258
264
  --range-timeline-row-height: var(--control-height-lg);
259
265
  --range-timeline-bar-height: var(--control-height-sm);
260
266
 
267
+ --range-timeline-bar-muted-alpha: 0.45;
268
+ --range-timeline-bar-overdue-width: var(--stroke-md);
269
+
270
+ --range-timeline-plan-border-width: var(--stroke-hairline);
271
+ --range-timeline-early-marker-inline-size: var(--stroke-md);
272
+
261
273
  --range-timeline-border-color: var(--border);
262
274
 
263
275
  --range-timeline-grid-color: initial;
@@ -48,7 +48,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
48
48
  | CardBar | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
49
49
  | TimeRangePicker | ✓ | · | · | · | · | · | · | · | · | · |
50
50
  | VisuallyHidden | **✗** | N/A | N/A | N/A | · | · | · | · | · | · |
51
- | RangeTimeline | **✗** | · | N/A | N/A | · | · | · | · | · | · |
51
+ | RangeTimeline | **✗** | · | · | N/A | · | · | · | · | · | · |
52
52
  | PageContainer | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
53
53
  | PageCover | ✓ | · | N/A | N/A | · | · | · | · | · | · |
54
54
  | Flex | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
@@ -1,4 +1,4 @@
1
- import type { CSSProperties } from "react";
1
+ import { useState, type CSSProperties } from "react";
2
2
 
3
3
  import { useTranslation } from "@godxjp/ui/i18n";
4
4
  import {
@@ -11,6 +11,7 @@ import {
11
11
  RangeTimeline,
12
12
  type TimelineItem,
13
13
  } from "@godxjp/ui/data-display";
14
+ import { Switch } from "@godxjp/ui/data-entry";
14
15
  import { Text } from "@godxjp/ui/general";
15
16
  import { Flex, PageContainer } from "@godxjp/ui/layout";
16
17
  import { ArrowRightLeft, MessageSquare } from "lucide-react";
@@ -90,6 +91,7 @@ const richItems: TimelineItem[] = [
90
91
 
91
92
  export default function Demo() {
92
93
  const { t, locale } = useTranslation();
94
+ const [gantt, setGantt] = useState({ width: 280, busy: false });
93
95
  // An activity log (gh#1092): every row already happened, so none is "done" or "upcoming" —
94
96
  // `status: "log"` keeps the neutral dot and adds no screen-reader status prefix.
95
97
  const logItems: TimelineItem[] = [
@@ -270,6 +272,87 @@ export default function Demo() {
270
272
  />
271
273
  </CardContent>
272
274
  </Card>
275
+ <Card>
276
+ <CardHeader>
277
+ <CardTitle level={2}>{t("rangeTimelineDocs.resizeTitle")}</CardTitle>
278
+ <CardDescription>{t("rangeTimelineDocs.resizeDescription")}</CardDescription>
279
+ </CardHeader>
280
+ <CardContent>
281
+ <Flex direction="col" gap="sm">
282
+ <Switch
283
+ checked={gantt.busy}
284
+ onCheckedChange={(busy) => setGantt((state) => ({ ...state, busy }))}
285
+ aria-label={t("rangeTimelineDocs.busyToggle")}
286
+ />
287
+ <RangeTimeline
288
+ label={t("rangeTimeline.schedule")}
289
+ columns={Array.from({ length: 14 }, (_, index) => ({
290
+ label: new Intl.NumberFormat(locale).format(index + 1),
291
+ units: 1,
292
+ }))}
293
+ rows={[
294
+ {
295
+ id: "r1",
296
+ label: t("rangeTimelineDocs.longTask"),
297
+ start: 1,
298
+ end: 6,
299
+ startLabel: "",
300
+ endLabel: "",
301
+ color: "#2563eb",
302
+ tooltip: t("rangeTimelineDocs.longTask"),
303
+ },
304
+ {
305
+ id: "r3",
306
+ label: t("rangeTimelineDocs.doneTask"),
307
+ start: 0,
308
+ end: 2,
309
+ startLabel: "",
310
+ endLabel: "",
311
+ color: "#16a34a",
312
+ muted: true,
313
+ },
314
+ {
315
+ id: "r4",
316
+ label: t("rangeTimelineDocs.lateTask"),
317
+ start: 3,
318
+ end: 8,
319
+ startLabel: "",
320
+ endLabel: "",
321
+ color: "#f59e0b",
322
+ overdue: true,
323
+ },
324
+ {
325
+ id: "r5",
326
+ label: t("rangeTimelineDocs.plannedTask"),
327
+ start: 2,
328
+ end: 9,
329
+ startLabel: "",
330
+ endLabel: "",
331
+ color: "#7c3aed",
332
+ plan: { start: 2, end: 6 },
333
+ },
334
+ {
335
+ id: "r2",
336
+ label: t("rangeTimelineDocs.undatedTask"),
337
+ start: null,
338
+ end: null,
339
+ startLabel: "",
340
+ endLabel: "",
341
+ },
342
+ ]}
343
+ resizableLabel={{ min: 180, max: 520 }}
344
+ labelWidth={gantt.width}
345
+ onLabelWidthChange={(width) => setGantt((state) => ({ ...state, width }))}
346
+ busy={gantt.busy}
347
+ />
348
+ <Text size="sm" tone="muted">
349
+ {t("rangeTimelineDocs.width", {
350
+ width: new Intl.NumberFormat(locale).format(gantt.width),
351
+ })}
352
+ </Text>
353
+ </Flex>
354
+ </CardContent>
355
+ </Card>
273
356
 
274
357
  {/* sticky sits OUTSIDE a Card on purpose: Card clips (`overflow: hidden`), and a clipping
275
358
  ancestor becomes the box the header sticks to — the same limit as antd Table sticky. */}
@@ -1689,5 +1689,16 @@
1689
1689
  },
1690
1690
  "sidebarDocs": {
1691
1691
  "newEntry": "New journal entry"
1692
+ },
1693
+ "rangeTimelineDocs": {
1694
+ "resizeTitle": "Resizable label column, loading, undated rows",
1695
+ "resizeDescription": "Drag the divider in the header (or focus it and use the arrow keys, Shift for bigger steps, Home/End) to widen the label column. The width is controlled, so an app can remember it per user. The switch marks a range as loading; a row without dates shows a hint instead of a bar.",
1696
+ "busyToggle": "Loading a new range",
1697
+ "longTask": "EXSELI-81 Rebuild the contact form and its confirmation email",
1698
+ "undatedTask": "EXSELI-82 Not scheduled yet",
1699
+ "width": "Label column: {width}px",
1700
+ "doneTask": "EXSELI-79 Release notes (done)",
1701
+ "lateTask": "EXSELI-80 Data migration (overdue)",
1702
+ "plannedTask": "EXSELI-84 Payment API (plan 3–7, actual 3–10)"
1692
1703
  }
1693
1704
  }
@@ -1682,5 +1682,16 @@
1682
1682
  },
1683
1683
  "sidebarDocs": {
1684
1684
  "newEntry": "仕訳を追加"
1685
+ },
1686
+ "rangeTimelineDocs": {
1687
+ "resizeTitle": "ラベル列の幅変更・読み込み中・日付なしの行",
1688
+ "resizeDescription": "ヘッダーの区切りをドラッグ(またはフォーカスして矢印キー、Shiftで大きく、Home/End)するとラベル列の幅が変わります。幅は制御プロパティなので、アプリ側でユーザーごとに保存できます。スイッチは新しい期間の読み込み中を示し、日付のない行はバーの代わりに案内を表示します。",
1689
+ "busyToggle": "新しい期間を読み込み中",
1690
+ "longTask": "EXSELI-81 問い合わせフォームと確認メールの改修",
1691
+ "undatedTask": "EXSELI-82 まだ日程未定",
1692
+ "width": "ラベル列: {width}px",
1693
+ "doneTask": "EXSELI-79 リリースノート(完了)",
1694
+ "lateTask": "EXSELI-80 データ移行(期限超過)",
1695
+ "plannedTask": "EXSELI-84 決済API(予定 3–7、実績 3–10)"
1685
1696
  }
1686
1697
  }
@@ -1682,5 +1682,16 @@
1682
1682
  },
1683
1683
  "sidebarDocs": {
1684
1684
  "newEntry": "Thêm bút toán"
1685
+ },
1686
+ "rangeTimelineDocs": {
1687
+ "resizeTitle": "Cột nhãn đổi độ rộng, đang tải, hàng chưa có ngày",
1688
+ "resizeDescription": "Kéo vạch ngăn ở header (hoặc focus rồi dùng phím mũi tên, Shift để bước lớn, Home/End) để mở rộng cột nhãn. Độ rộng là prop controlled nên app có thể lưu theo từng người dùng. Công tắc đánh dấu đang tải khoảng mới; hàng chưa có ngày hiện gợi ý thay cho thanh.",
1689
+ "busyToggle": "Đang tải khoảng mới",
1690
+ "longTask": "EXSELI-81 Làm lại form liên hệ và email xác nhận",
1691
+ "undatedTask": "EXSELI-82 Chưa lên lịch",
1692
+ "width": "Cột nhãn: {width}px",
1693
+ "doneTask": "EXSELI-79 Ghi chú phát hành (xong)",
1694
+ "lateTask": "EXSELI-80 Chuyển dữ liệu (quá hạn)",
1695
+ "plannedTask": "EXSELI-84 API thanh toán (kế hoạch 3–7, thực tế 3–10)"
1685
1696
  }
1686
1697
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "31.29.0",
4
- "godxUiMcp": "31.29.0",
3
+ "version": "31.30.0",
4
+ "godxUiMcp": "31.30.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",