@qijenchen/design-system 0.1.0-beta.143 → 0.1.0-beta.145

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.
Files changed (52) hide show
  1. package/dist/components/DateGrid/date-grid.d.ts +15 -1
  2. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  3. package/dist/components/DateGrid/date-grid.js +77 -22
  4. package/dist/components/DateGrid/date-grid.js.map +1 -1
  5. package/dist/components/DateGrid/index.js +5 -1
  6. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  7. package/dist/components/DatePicker/date-picker.js +58 -19
  8. package/dist/components/DatePicker/date-picker.js.map +1 -1
  9. package/dist/components/DatePicker/range-preview.d.ts +46 -0
  10. package/dist/components/DatePicker/range-preview.d.ts.map +1 -0
  11. package/dist/components/DatePicker/range-preview.js +41 -0
  12. package/dist/components/DatePicker/range-preview.js.map +1 -0
  13. package/ds-canonical/fork/codex/agents/skills/visual-audit/references/audit-architecture.md +1 -1
  14. package/ds-canonical/fork/common/closed-tool-execution.mjs +31 -0
  15. package/ds-canonical/fork/consumer/lib/closed-tool-execution.mjs +31 -0
  16. package/ds-canonical/fork/consumer/lock.json +10 -10
  17. package/ds-canonical/fork/consumer/managed-files/2d1ef5617a5ce3c39ad9aae1c42af7d199f9270c0a0ccb490b63d62dbc6913db.blob +31 -0
  18. package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +189 -38
  19. package/ds-canonical/fork/consumer/managed-files/61c6aca6d7f04bedbf556eaa9d3043681fe48cbe0e93a8ccb1d69a7c5e23fdcf.blob +6 -1
  20. package/ds-canonical/fork/governance.lock +10 -10
  21. package/ds-canonical/fork/manifest.json +10 -10
  22. package/ds-canonical/fork/providers/claude/skills/visual-audit/references/audit-architecture.md +1 -1
  23. package/ds-canonical/fork/providers/codex/skills/visual-audit/references/audit-architecture.md +1 -1
  24. package/ds-canonical/fork/skills/visual-audit/references/audit-architecture.md +1 -1
  25. package/ds-canonical/references/focus-canonical.md +45 -8
  26. package/ds-canonical/references/principle-dim-map.json +5 -0
  27. package/ds-canonical/rules/meta-patterns.md +1 -1
  28. package/ds-canonical/rules/story-rules.md +7 -0
  29. package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +50 -20
  30. package/ds-canonical/skills/visual-audit/references/audit-architecture.md +1 -1
  31. package/ds-story-manifest.json +5 -3
  32. package/package.json +1 -1
  33. package/src/components/Breadcrumb/breadcrumb.stories.tsx +2 -0
  34. package/src/components/Button/button.stories.tsx +4 -0
  35. package/src/components/Calendar/calendar.stories.tsx +2 -0
  36. package/src/components/Combobox/combobox.stories.tsx +2 -0
  37. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +2 -2
  38. package/src/components/DateGrid/date-grid.spec.md +45 -3
  39. package/src/components/DateGrid/date-grid.tsx +102 -21
  40. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +15 -3
  41. package/src/components/DatePicker/date-picker.spec.md +33 -6
  42. package/src/components/DatePicker/date-picker.stories.tsx +54 -30
  43. package/src/components/DatePicker/date-picker.tsx +70 -18
  44. package/src/components/DatePicker/range-preview.ts +82 -0
  45. package/src/components/Dialog/dialog.stories.tsx +4 -0
  46. package/src/components/FileItem/file-item.stories.tsx +2 -0
  47. package/src/components/FileUpload/file-upload.stories.tsx +2 -0
  48. package/src/components/PeoplePicker/people-picker.stories.tsx +4 -0
  49. package/src/components/Steps/steps.stories.tsx +2 -0
  50. package/src/components/TreeView/tree-view.stories.tsx +2 -0
  51. package/src/patterns/overlay-surface/overlay-surface.stories.tsx +2 -0
  52. package/src/styles/base.css +17 -0
@@ -26,11 +26,12 @@ import 'react-day-picker/style.css';
26
26
  * |-------|------|-------|
27
27
  * | today | 數字下方藍色短桿 | `::after` pseudo bar(bg-primary,w-40% h-1.5px rounded-full,貼近數字底)|
28
28
  * | disabled | 灰底圓圈 + disabled 字色(跟 Button disabled 一致) | [&>button]:bg-disabled [&>button]:text-fg-disabled rounded-full |
29
- * | outside(非本月) | text-fg-muted(neutral-7)—— **僅在該日仍可點時**;不可點時讓位給 disabled(M24) | [&>button:not(:disabled):not([aria-disabled="true"])]:text-fg-muted |
29
+ * | outside(非本月) | 單月:text-fg-muted(neutral-7)—— **僅在該日仍可點、且未被選中時**(不可點讓位 disabled、選中讓位藍底白字,M24);**兩月以上不渲染**(numberOfMonths > 1 強制 showOutsideDays=false) | [&:not([data-selected])>button:not(:disabled):not([aria-disabled="true"])]:text-fg-muted |
30
30
  * | selected / range 端點 | 藍底白字圓 | [&>button]:bg-primary [&>button]:text-on-emphasis rounded-full |
31
31
  * | range middle | 灰底矩形 track(bg-neutral-selected = neutral-2),**高度 = cell 高度**(28×28 @ md) | before pseudo: `inset-y-0 -inset-x-[2px]` |
32
32
  * | range start/end 半圓 track | 左/右半圓 + selected 圓疊在上,**圓半徑 = button 半徑** | before pseudo: `rounded-l/r-full` + start `left-0 -right-[2px]` / end `-left-[2px] right-0`(向 middle 外擴 2px bridge gap)|
33
33
  * | hover(未選中) | 藍圈 outline | hover:ring-[1.5px] hover:ring-primary-hover(2026-07-07 統一:瞬時 hover = hover 階)|
34
+ * | range 預覽框(停留 / 焦點,只有 DatePicker.Range 會算出) | 同色同粗的藍色細框,把「點下去會變成」的區間框起來;兩端半圓、與 track 同高;停留日就是框的那一端(不再畫單格圈) | td `::after`(track 用 `::before`):`RANGE_PREVIEW_CLASSNAMES`,ring / inset 陰影 1.5px primary-hover(不用 border:1.5px 會被取整成 1px);user 2026-09-23 拍板 |
34
35
  *
35
36
  * ── Range track 高度 canonical(2026-05-03 v6,M8 4 家對照)──
36
37
  * Ant Design([picker source](https://github.com/ant-design/ant-design/blob/master/components/date-picker/style/panel.ts))/ Material X DateRangePicker([mui-x source](https://github.com/mui/mui-x/tree/master/packages/x-date-pickers-pro/src/DateRangeCalendar))/ Apple Calendar `@benchmark-unverified`(closed-source)/ Google Calendar `@benchmark-unverified`(closed-source)共識:
@@ -48,6 +49,19 @@ import 'react-day-picker/style.css';
48
49
  * 省垂直空間,使用者視線不需上下跳。世界級(Google Calendar / Apple / iOS 日期輸入)皆此佈局。
49
50
  */
50
51
  export type DateGridProps = React.ComponentProps<typeof DayPicker>;
52
+ export declare const RANGE_TRACK_CLASSNAMES: Readonly<{
53
+ start: string;
54
+ middle: string;
55
+ end: string;
56
+ }>;
57
+ export declare const RANGE_PREVIEW_CLASSNAMES: Readonly<{
58
+ start: string;
59
+ middle: string;
60
+ end: string;
61
+ single: string;
62
+ }>;
63
+ export declare const RANGE_PREVIEW_ARMED_CLASSNAME = "[&>button]:hover:!ring-0";
64
+ export declare const EMPHASIS_FOCUS_RING_CLASSNAME = "[&>button]:focus-visible:focus-ring-inset-emphasis";
51
65
  declare const DateGrid: React.ForwardRefExoticComponent<import("react-day-picker").DayPickerProps & React.RefAttributes<HTMLDivElement>>;
52
66
  export declare const dateGridMeta: {
53
67
  readonly component: "DateGrid";
@@ -1 +1 @@
1
- {"version":3,"file":"date-grid.d.ts","sourceRoot":"","sources":["../../../src/components/DateGrid/date-grid.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE5C,OAAO,4BAA4B,CAAA;AAKnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,MAAM,MAAM,aAAa,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,SAAS,CAAC,CAAA;AAUlE,QAAA,MAAM,QAAQ,kHA2NZ,CAAA;AAKF,eAAO,MAAM,YAAY;;;;;;;;;;;CAiBf,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"date-grid.d.ts","sourceRoot":"","sources":["../../../src/components/DateGrid/date-grid.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE5C,OAAO,4BAA4B,CAAA;AAKnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,MAAM,MAAM,aAAa,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,SAAS,CAAC,CAAA;AAclE,eAAO,MAAM,sBAAsB;;;;EAiBjC,CAAA;AAgBF,eAAO,MAAM,wBAAwB;;;;;EAMnC,CAAA;AAMF,eAAO,MAAM,6BAA6B,6BAA6B,CAAA;AAIvE,eAAO,MAAM,6BAA6B,uDAAuD,CAAA;AAUjG,QAAA,MAAM,QAAQ,kHA4OZ,CAAA;AAKF,eAAO,MAAM,YAAY;;;;;;;;;;;CAiBf,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,CAAA"}
@@ -5,6 +5,43 @@ import { ChevronRight, ChevronLeft } from "lucide-react";
5
5
  /* empty css */
6
6
  import { cn } from "../../lib/utils.js";
7
7
  import { Button } from "../Button/button.js";
8
+ const RANGE_TRACK_CLASSNAMES = Object.freeze({
9
+ start: cn(
10
+ "before:content-[''] before:absolute before:inset-y-0",
11
+ "before:left-0 before:-right-[2px]",
12
+ "before:bg-neutral-selected before:pointer-events-none",
13
+ "before:rounded-l-full"
14
+ // ← stadium 左半圓 matches button 圓的左半弧
15
+ ),
16
+ middle: cn(
17
+ "before:content-[''] before:absolute before:inset-y-0 before:-inset-x-[2px]",
18
+ "before:bg-neutral-selected before:pointer-events-none"
19
+ ),
20
+ end: cn(
21
+ "before:content-[''] before:absolute before:inset-y-0",
22
+ "before:-left-[2px] before:right-0",
23
+ "before:bg-neutral-selected before:pointer-events-none",
24
+ "before:rounded-r-full"
25
+ // ← 鏡像
26
+ )
27
+ });
28
+ const PREVIEW_FRAME = cn(
29
+ "after:content-[''] after:absolute after:inset-y-0 after:pointer-events-none",
30
+ // 停留日的單格圈讓位給框的端點(preview 的四種格都可能是停留日)
31
+ "[&>button]:hover:!ring-0"
32
+ );
33
+ const PREVIEW_STROKE = "after:ring-inset after:ring-[1.5px] after:ring-primary-hover";
34
+ const CLIP_INNER_RIGHT = "after:[clip-path:polygon(0_0,100%_0,100%_1.5px,calc(100%_-_2px)_1.5px,calc(100%_-_2px)_calc(100%_-_1.5px),100%_calc(100%_-_1.5px),100%_100%,0_100%)]";
35
+ const CLIP_INNER_LEFT = "after:[clip-path:polygon(0_0,100%_0,100%_100%,0_100%,0_calc(100%_-_1.5px),2px_calc(100%_-_1.5px),2px_1.5px,0_1.5px)]";
36
+ const RANGE_PREVIEW_CLASSNAMES = Object.freeze({
37
+ start: cn(PREVIEW_FRAME, PREVIEW_STROKE, "after:left-0 after:-right-[2px] after:rounded-l-full", CLIP_INNER_RIGHT),
38
+ // 中段只有上下兩條線:兩個各 1.5px 的 inset 陰影(ring 畫不了單邊)
39
+ middle: cn(PREVIEW_FRAME, "after:-inset-x-[2px] after:shadow-[inset_0_1.5px_0_0_var(--primary-hover),inset_0_-1.5px_0_0_var(--primary-hover)]"),
40
+ end: cn(PREVIEW_FRAME, PREVIEW_STROKE, "after:-left-[2px] after:right-0 after:rounded-r-full", CLIP_INNER_LEFT),
41
+ single: cn(PREVIEW_FRAME, PREVIEW_STROKE, "after:inset-x-0 after:rounded-full")
42
+ });
43
+ const RANGE_PREVIEW_ARMED_CLASSNAME = "[&>button]:hover:!ring-0";
44
+ const EMPHASIS_FOCUS_RING_CLASSNAME = "[&>button]:focus-visible:focus-ring-inset-emphasis";
8
45
  const ZH_FULL_DATE = new Intl.DateTimeFormat("zh-TW", { dateStyle: "full" });
9
46
  const ZH_MONTH_YEAR = new Intl.DateTimeFormat("zh-TW", { year: "numeric", month: "long" });
10
47
  const ZH_WEEKDAY = new Intl.DateTimeFormat("zh-TW", { weekday: "long" });
@@ -13,12 +50,14 @@ const DateGrid = React.forwardRef(function DateGrid2({
13
50
  classNames,
14
51
  labels,
15
52
  showOutsideDays = true,
53
+ numberOfMonths,
16
54
  ...props
17
55
  }, _ref) {
18
56
  return /* @__PURE__ */ jsx(
19
57
  DayPicker,
20
58
  {
21
- showOutsideDays,
59
+ showOutsideDays: (numberOfMonths ?? 1) > 1 ? false : showOutsideDays,
60
+ numberOfMonths,
22
61
  navLayout: "around",
23
62
  className: cn(className),
24
63
  classNames: {
@@ -68,7 +107,10 @@ const DateGrid = React.forwardRef(function DateGrid2({
68
107
  month_grid: cn(
69
108
  "border-separate border-spacing-1 -m-1",
70
109
  "[&_tr>td:first-child]:before:!left-0",
71
- "[&_tr>td:last-child]:before:!right-0"
110
+ "[&_tr>td:last-child]:before:!right-0",
111
+ // 預覽框(td ::after)在列首 / 列尾同樣不得溢出面板留白;換列處框是開口的,與 track 同款
112
+ "[&_tr>td:first-child]:after:!left-0",
113
+ "[&_tr>td:last-child]:after:!right-0"
72
114
  ),
73
115
  weekdays: "",
74
116
  // thead default
@@ -97,7 +139,17 @@ const DateGrid = React.forwardRef(function DateGrid2({
97
139
  // Hover 藍圈 1.5px(對齊 Apple HIG / Ant `@benchmark-unverified` visual ring measurement)— ring 在 button 之上 + 透明 bg 不擋 range track
98
140
  // 2026-07-07 user 拍板統一:瞬時 hover 進 primary 家族 = hover 階(FileUpload dropzone /
99
141
  // Slider thumb hover 同族;base 專屬持續選中與 focus)——ring-primary → ring-primary-hover
100
- "hover:ring-[1.5px] hover:ring-primary-hover hover:bg-transparent"
142
+ "hover:ring-[1.5px] hover:ring-primary-hover hover:bg-transparent",
143
+ // 鍵盤焦點框往內畫(focus-canonical「問題二」):格與格只隔 4px,而區間 track(::before)與預覽框(::after)
144
+ // 就跑在那 4px 縫裡 —— 往外畫的 2px 框正好壓在框線上(user 2026-09-23:「date 的鍵盤焦點感覺要改成
145
+ // 往內畫的那種,否則會跟區間藍框有視覺衝突」)。藍底格另走 EMPHASIS_FOCUS_RING_CLASSNAME(白線退 3px)。
146
+ "focus-visible:focus-ring-inset",
147
+ // 命中區外擴 2px 補滿格間 4px 縫隙(user 2026-09-23:「從某日水平移動到其隔日,藍色的區間框線都會閃動一下」):
148
+ // 停留日掛在 button 的 mouseenter / mouseleave(react-day-picker 只給這一層),指標跨格時只要有一次 mousemove 落在
149
+ // border-spacing 的縫裡就先 leave 再 enter,整條框先卸掉一幀再補回。框的畫法早就用 −2px 跨過縫隙把線接起來,
150
+ // 命中幾何卻沒跟上 —— 這一條讓 hit 與 paint 同一個數字。不畫任何東西(基準圖不動);Ant 整個 td 可命中、
151
+ // MUI 只在離開整個日曆才清,都沒有「縫隙裡沒有任何一天」這個狀態。閘:datepicker-range-preview.mjs「跨格不閃」。
152
+ "before:content-[''] before:absolute before:-inset-[2px]"
101
153
  ),
102
154
  // today:藍色 underline bar 貼近數字
103
155
  today: cn(
@@ -117,11 +169,15 @@ const DateGrid = React.forwardRef(function DateGrid2({
117
169
  // 依 M24「State 顯著性 precedence:disabled > muted > emphasis」與本元件 spec.md:106
118
170
  //(「outside…比 disabled 弱:outside 仍可 hover / 可點」)、:201(disabled → text-fg-disabled(M24)),
119
171
  // outside 的淡化**只適用於還能點的日子**,故加 :not() 前提而非用 !important 硬壓。
120
- outside: '[&>button:not(:disabled):not([aria-disabled="true"])]:text-fg-muted',
172
+ // 2026-09-23 再加 `:not([data-selected])`:淡字是裝飾、選中是 state(M24 state 勝裝飾)—— 單月裡選中日落在鄰月位置時
173
+ // 仍是藍底白字圓,不能被淡字選擇器(特異性 (0,3,1))壓成灰字。RDP `mode="range"` 單月的鄰月中段日也掛 selected,
174
+ // 字色因此跟當月中段一樣深(不淡化);兩月時鄰月日子根本不渲染,這條只在單月 range 出現。
175
+ outside: '[&:not([data-selected])>button:not(:disabled):not([aria-disabled="true"])]:text-fg-muted',
121
176
  // Selected(single 或 range 端點):button 藍底白字圓
122
177
  selected: cn(
123
178
  "[&>button]:bg-primary [&>button]:text-on-emphasis",
124
- "[&>button]:hover:bg-primary-hover [&>button]:hover:!ring-0"
179
+ "[&>button]:hover:bg-primary-hover [&>button]:hover:!ring-0",
180
+ EMPHASIS_FOCUS_RING_CLASSNAME
125
181
  ),
126
182
  disabled: cn(
127
183
  "[&>button]:bg-disabled [&>button]:text-fg-disabled [&>button]:cursor-not-allowed",
@@ -136,23 +192,11 @@ const DateGrid = React.forwardRef(function DateGrid2({
136
192
  // 右側矩形 bridge 2px to middle → 跟 middle pseudo 連續
137
193
  // Cell 的 top-left + bottom-left corner triangle:pseudo 不蓋 + button 不蓋 →
138
194
  // popover white 顯露(乾淨 breathing,跟 outside-of-range cells 一致視覺)
139
- range_start: cn(
140
- "before:content-[''] before:absolute before:inset-y-0",
141
- "before:left-0 before:-right-[2px]",
142
- "before:bg-neutral-selected before:pointer-events-none",
143
- "before:rounded-l-full"
144
- // ← stadium 左半圓 matches button 圓的左半弧
145
- ),
146
- range_end: cn(
147
- "before:content-[''] before:absolute before:inset-y-0",
148
- "before:-left-[2px] before:right-0",
149
- "before:bg-neutral-selected before:pointer-events-none",
150
- "before:rounded-r-full"
151
- // ← 鏡像
152
- ),
195
+ // class 本體住在檔頭 RANGE_TRACK_CLASSNAMES(DatePicker.Range 消費同一份)
196
+ range_start: RANGE_TRACK_CLASSNAMES.start,
197
+ range_end: RANGE_TRACK_CLASSNAMES.end,
153
198
  range_middle: cn(
154
- "before:content-[''] before:absolute before:inset-y-0 before:-inset-x-[2px]",
155
- "before:bg-neutral-selected before:pointer-events-none",
199
+ RANGE_TRACK_CLASSNAMES.middle,
156
200
  // 2026-07-07 user 拍板:range 中段的 today bar 維持藍(切白只屬藍底選中日;白條在
157
201
  // neutral-selected 淺灰底上近乎隱形 = today 標記消失)。!important 確定性壓過 today 的
158
202
  // data-selected 切白規則(同權重靠 stylesheet 順序不可靠)。Ant 源碼實錘:cell-today
@@ -164,14 +208,19 @@ const DateGrid = React.forwardRef(function DateGrid2({
164
208
  // 上方 selected 的 `[&>button]:hover:!ring-0` 一併壓制 hover ring(與 selected 一致
165
209
  // 的 hover 抑制),非原註解宣稱的「hover ring 仍顯示」;today bar 亦被 data-selected
166
210
  // selector 切 on-emphasis(見 spec「組合狀態」段)。
167
- "[&>button]:!bg-transparent [&>button]:!text-foreground"
211
+ "[&>button]:!bg-transparent [&>button]:!text-foreground",
212
+ // 中段日同樣掛 selected(見上),會一併吃到 selected 的白線焦點 —— 但中段底是淺灰、白線看不見,
213
+ // 焦點要回到一般的往內 2px 藍線;同權重靠順序不可靠,用 ! 壓過。
214
+ "[&>button]:focus-visible:!focus-ring-inset"
168
215
  ),
169
216
  hidden: "invisible",
170
217
  ...classNames
171
218
  },
172
219
  labels: {
173
220
  labelPrevious: () => "上一個月",
221
+ // i18n-allow: DS default; consumer override via labels prop
174
222
  labelNext: () => "下一個月",
223
+ // i18n-allow: DS default; consumer override via labels prop
175
224
  // 鏡射 RDP default 結構(labels/labelDayButton.js:today 前綴 / selected 後綴),文案中文化
176
225
  labelDayButton: (date, modifiers) => {
177
226
  let label = ZH_FULL_DATE.format(date);
@@ -190,7 +239,9 @@ const DateGrid = React.forwardRef(function DateGrid2({
190
239
  // 以下非 default 渲染面(captionLayout dropdown / showWeekNumber 由 consumer 經
191
240
  // {...props} 開啟)— 一併補齊,避免開啟後 SR 中英夾雜
192
241
  labelMonthDropdown: () => "選擇月份",
242
+ // i18n-allow: DS default; consumer override via labels prop
193
243
  labelYearDropdown: () => "選擇年份",
244
+ // i18n-allow: DS default; consumer override via labels prop
194
245
  labelWeekNumber: (weekNumber) => `第 ${weekNumber} 週`,
195
246
  labelWeekNumberHeader: () => "週數",
196
247
  ...labels
@@ -250,6 +301,10 @@ const dateGridMeta = {
250
301
  };
251
302
  export {
252
303
  DateGrid,
304
+ EMPHASIS_FOCUS_RING_CLASSNAME,
305
+ RANGE_PREVIEW_ARMED_CLASSNAME,
306
+ RANGE_PREVIEW_CLASSNAMES,
307
+ RANGE_TRACK_CLASSNAMES,
253
308
  dateGridMeta
254
309
  };
255
310
  //# sourceMappingURL=date-grid.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"date-grid.js","sources":["../../../src/components/DateGrid/date-grid.tsx"],"sourcesContent":["/**\n * @internal — DS-internal 單元(per `packages/design-system/ds-canonical/rules/ui-development.md` Public vs Internal canonical;spec frontmatter `isInternal`)。\n * 不進 root barrel front-door;由 DatePicker wrap 消費,end-user app 請用 wrapper 元件。\n */\n// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// M22 retrofit DONE 2026-05-03 v11(real source URLs added inline at lines 38 + 111)\nimport * as React from 'react'\nimport { DayPicker } from 'react-day-picker'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport 'react-day-picker/style.css'\n\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\n\n/**\n * DateGrid — DayPicker 包裝,用本 DS token 覆寫預設視覺。\n *\n * ── 視覺對照(ref/datepicker.png,2026-04-21 rewrite)──\n *\n * | 區塊 | 規格 |\n * |------|------|\n * | Outer padding | `p-3`(12px 四邊對稱) |\n * | Nav + Month caption row | h-field-xs(24px)單行,chevron(xs)分居左右 / 月份置中垂直對齊 |\n * | Nav → Weekday gap | 12px(month_caption mb-3) |\n * | Weekday | text-body(14px)text-foreground font-medium(neutral-9,同 caption 權重;撤銷 v3 fg-secondary) |\n * | Cell gap(水平 + 垂直)| 4px(table-native border-separate border-spacing-1;非 gap — grid 會 break,見下方 v7 註) |\n * | Day cell size | h-field-sm w-[var(--field-height-sm)](28×28 md / 32×32 lg) |\n * | Day button | rounded-full 填滿 cell |\n *\n * ── 五種 cell state canonical ──\n *\n * | State | 視覺 | Token |\n * |-------|------|-------|\n * | today | 數字下方藍色短桿 | `::after` pseudo bar(bg-primary,w-40% h-1.5px rounded-full,貼近數字底)|\n * | disabled | 灰底圓圈 + disabled 字色(跟 Button disabled 一致) | [&>button]:bg-disabled [&>button]:text-fg-disabled rounded-full |\n * | outside(非本月) | text-fg-muted(neutral-7)—— **僅在該日仍可點時**;不可點時讓位給 disabled(M24) | [&>button:not(:disabled):not([aria-disabled=\"true\"])]:text-fg-muted |\n * | selected / range 端點 | 藍底白字圓 | [&>button]:bg-primary [&>button]:text-on-emphasis rounded-full |\n * | range middle | 灰底矩形 track(bg-neutral-selected = neutral-2),**高度 = cell 高度**(28×28 @ md) | before pseudo: `inset-y-0 -inset-x-[2px]` |\n * | range start/end 半圓 track | 左/右半圓 + selected 圓疊在上,**圓半徑 = button 半徑** | before pseudo: `rounded-l/r-full` + start `left-0 -right-[2px]` / end `-left-[2px] right-0`(向 middle 外擴 2px bridge gap)|\n * | hover(未選中) | 藍圈 outline | hover:ring-[1.5px] hover:ring-primary-hover(2026-07-07 統一:瞬時 hover = hover 階)|\n *\n * ── Range track 高度 canonical(2026-05-03 v6,M8 4 家對照)──\n * Ant Design([picker source](https://github.com/ant-design/ant-design/blob/master/components/date-picker/style/panel.ts))/ Material X DateRangePicker([mui-x source](https://github.com/mui/mui-x/tree/master/packages/x-date-pickers-pro/src/DateRangeCalendar))/ Apple Calendar `@benchmark-unverified`(closed-source)/ Google Calendar `@benchmark-unverified`(closed-source)共識:\n * **range track 為連續 stadium**,跟 selected 圓緊貼成連續 pill。\n * 實作:bg 走 `before:` pseudo 用 `inset-y-0`(滿 cell 高度)+ `-inset-x-[2px]`\n * (左右各外擴 2px bridge 相鄰 cell gap)→ 相鄰 cell 的 pseudo 連續銜接 = 橫向連貫;\n * day cell 本身就是 button 容器(L102-105 day = h-field-sm cell),故滿 cell 高 = 滿 button 高。\n *\n * ── Range track 色用 bg-neutral-selected(= neutral-2)──\n * 對齊 TimePicker 選中項目樣式(semantic `--neutral-selected` = neutral-2,semantic.css L84/L340)。\n * 同 DS 內「持續選中」語意 token,不自開 neutral-3 tier。\n *\n * ── 為什麼 nav 放頂部 + 年月垂直置中(不 separate 兩行)──\n * ref/datepicker.png:chevron prev / 月份 / chevron next 同一行,24px 高(xs field height)。\n * 省垂直空間,使用者視線不需上下跳。世界級(Google Calendar / Apple / iOS 日期輸入)皆此佈局。\n */\n\nexport type DateGridProps = React.ComponentProps<typeof DayPicker>\n\n// ── SR label 中文 formatter(2026-07-05)──\n// 走 Intl.DateTimeFormat(對齊 TimePicker / Calendar 既有 canonical,不引第二套 date lib);\n// module-level 建一次 — label fn 每個 day cell 都會呼叫,避免 per-call new formatter。\nconst ZH_FULL_DATE = new Intl.DateTimeFormat('zh-TW', { dateStyle: 'full' })\nconst ZH_MONTH_YEAR = new Intl.DateTimeFormat('zh-TW', { year: 'numeric', month: 'long' })\nconst ZH_WEEKDAY = new Intl.DateTimeFormat('zh-TW', { weekday: 'long' })\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGrid(\n {\n className,\n classNames,\n labels,\n showOutsideDays = true,\n ...props\n },\n _ref,\n) {\n // Note: react-day-picker v9 DayPicker 未對外 forward ref 到單一 DOM 節點(內部有多 div),\n // 故 ref 簽名保留但不附著(符合 DS 統一 forwardRef 慣例;真要取 DOM 用 wrapper 包)。\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n // navLayout=\"around\" = prev 渲染在首月(displayIndex===0)caption 左、next 渲染在末月(displayIndex===numberOfMonths-1)caption 右;單月時兩鍵同 caption 兩側\n // 取代先前 absolute 定位覆蓋整個 months 容器導致箭頭垂直置中於中段的 bug\n navLayout=\"around\"\n // 根**不帶內距**:內距搬到每一張月曆自己身上(見下方 `month`)。\n // 2026-09-18 user 裁示,原話:「我覺得真的要做的話,邏輯就是這樣,而不是另外加一個 token,\n // 反而造成漂移,因為視覺就是要在各種情況營造對稱感吧?」\n className={cn(className)}\n classNames={{\n // **兩張月曆之間不設 gap**(2026-09-18 user 裁示)。\n // 每張月曆自己四周留 `--item-px`,所以彼此之間自然就是**兩份內距**(md 12+12 = 24),\n // 而且「單張」與「並排」看起來完全一樣 —— 對稱是結構保證的,不是靠一個要人維護的數字。\n // 這也是為什麼**不需要**月間距 token:少一個可以漂的東西。\n // 世界級同款:Ant Design 就是這個作法(兩張各自帶左右內距、中間不設 gap),\n // 且 v4(內距 12 → 間距 24)與 v5(18 → 36)跨大版本維持 1:2,是刻意的比例。\n months: 'flex flex-col sm:flex-row',\n // Month:relative 讓 prev/next 按鈕 absolute 定位到 month 右上/左上(navLayout=\"around\")。\n // **內距住在這裡**(2026-09-18 搬家):每張月曆自己四周留 `--item-px`,\n // 於是「面板邊 → 第一格」與「月曆 → 月曆」用的是同一顆東西,後者自然是前者的兩份。\n month: 'flex flex-col relative p-[var(--item-px,var(--field-px))]',\n // Month caption:單行置中 h-field-xs,prev/next 按鈕 absolute 從兩側貼齊\n month_caption: 'flex items-center justify-center h-field-xs mb-3',\n caption_label: 'text-body font-medium',\n // ── Prev/Next button(canonical 2026-05-03 v6,user audit fix)──\n // RDP 把這 className apply 到 <Button> 本身(不是 wrapper),所以用 className 直接套\n // 定位類(absolute top/left/right-0)。muted 色透過 Button override 內部加,不用 [&>button] 黑魔法。\n // nav 的 absolute 定位是相對 month 的 **padding box**(= 盒子最外緣),\n // 內距搬到 month 之後不補這個偏移,chevron 會貼到月曆盒最外緣、比日期格往外 12px(2026-09-18 實測確認)。\n // 讀同一顆 `--item-px`,所以它跟日期格永遠在同一條線上。\n button_previous: 'absolute top-[var(--item-px,var(--field-px))] left-[var(--item-px,var(--field-px))] z-[1]',\n button_next: 'absolute top-[var(--item-px,var(--field-px))] right-[var(--item-px,var(--field-px))] z-[1]',\n // ── Grid layout(canonical 2026-05-03 v7,純 table-native)──\n // RDP v9 month_grid = <table>。v6 試 grid 在 tr 上但 break border-spacing(grid 蓋掉\n // table-row layout)。乾淨修:**純 table layout** + `border-spacing-1`(4px H+V,table-native)。\n // 所有 cells 自動同寬同高(td 的 w/h-field-sm),無 grid hack。\n //\n // `-m-1` 抵銷**最外圈**的 border-spacing(2026-09-18 user 拍板)。\n // 為什麼需要它:`border-spacing` 是「格與格之間 4px」,但 CSS 連**最外圈也各給 4px** ——\n // 於是日期格的盒子落在 容器 12 + 4 = 16,而同一個面板的上下月 chevron 貼著容器的 12。\n // 規格(本檔 spec「Spacing canonical」原文,2026-05-03 v8)寫的是\n //「四邊對稱:chevron 按鈕到邊距 = 最左最右日期 cell 到邊距(12px)」—— 兩者都該是 12。\n // 那 4px 外圈**從來沒有人決定過**(全 repo 只有 2026-09-18 的 spec 段落提到它),是 `border-separate` 的副作用。\n // 負 margin 把 table 的盒子往外拉 4px:**格與格之間仍然是 4px,但最外圈歸零**,第一格因此落在 12。\n // 對照上游:我們包的 `react-day-picker` 自己是 `border-collapse: collapse`,\n // 它 457 行的 style.css 裡 `border-spacing` 出現 **0 次**(2026-09-18 讀 node_modules 原始碼);\n // IBM Carbon 的日曆同樣是第一格貼齊容器內距(實測外溢 0)。外溢是我們自己加的,不是慣例。\n // 列頭尾的 bridge 夾住:`-2px` 是用來跨過格與格之間的 4px 縫去接鄰格,\n // 但**列的第一格左邊、最後一格右邊沒有鄰格**,再往外就越過容器內距 —— 抵銷最外圈之後會直接溢出面板留白(實測 2px)。\n // 掛在 month_grid 而不是三個 range 狀態各寫一次:2026-09-18 實測 react-day-picker **不會**把\n // 我們加在 `classNames.range_*` 裡的這兩個 class 帶到 `<td>` 上(bundle 有、`cn()` 不吃、chunk 也載對了,\n // 但 DOM 完全查不到),而 `month_grid` 的 class 確定會落地(同一行的 `-m-1` 就是證據)。\n // 一處宣告、三種 range 狀態一起管,也少兩個要同步的地方。\n month_grid: cn(\n 'border-separate border-spacing-1 -m-1',\n '[&_tr>td:first-child]:before:!left-0',\n '[&_tr>td:last-child]:before:!right-0',\n ),\n weekdays: '', // thead default\n weekday: cn(\n // text-foreground + font-medium 對齊 DS 一致設計語言(2026-05-03 user audit):\n // weekday 列標跟 caption「April 2026」同視覺權重(都屬 calendar header 區),\n // 不弱化(撤銷 v3 fg-secondary 的 mistake)。對齊 icon-only Button 預設 neutral-9 一致。\n 'text-foreground text-body font-medium',\n // h-field-sm(md=28/lg=32 隨 density):對齊 day cell 同 token 同 row cadence\n // (spec:127 既定 canonical;Ant 36/36、Carbon 40/40 header/day 同節奏;2026-07-14 修 h-7 固定 drift)\n 'h-[var(--field-height-sm)] align-middle text-center',\n ),\n week: '', // tr default\n // Cell **就是** button 容器(28×28 @ md / 32×32 @ lg),`relative` 讓 before pseudo 定位\n // ── h-field-sm 對齊 user spec(28×28 @ md)— v3 用 h-field-md (32×32) 是錯的\n day: cn(\n 'h-field-sm w-[var(--field-height-sm)] p-0 text-center relative',\n ),\n day_button: cn(\n // absolute inset-0 = 完全填滿 cell(naked button,無 inset 4px 空隙)\n // z-[1] 讓 button 疊在 range track `before:` pseudo 之上\n 'absolute inset-0 z-[1] flex items-center justify-center',\n // hover 底色瞬間切換,不做過渡(user 2026-09-10 拍板「第三題改成全部瞬間」;SSOT = tokens/motion/motion.spec.md「hover 回饋不做過渡」)\n 'font-normal text-body rounded-full',\n // Hover 藍圈 1.5px(對齊 Apple HIG / Ant `@benchmark-unverified` visual ring measurement)— ring 在 button 之上 + 透明 bg 不擋 range track\n // 2026-07-07 user 拍板統一:瞬時 hover 進 primary 家族 = hover 階(FileUpload dropzone /\n // Slider thumb hover 同族;base 專屬持續選中與 focus)——ring-primary → ring-primary-hover\n 'hover:ring-[1.5px] hover:ring-primary-hover hover:bg-transparent',\n ),\n // today:藍色 underline bar 貼近數字\n today: cn(\n \"[&>button]:after:content-['']\",\n '[&>button]:after:absolute',\n '[&>button]:after:bottom-[5px] [&>button]:after:left-1/2 [&>button]:after:-translate-x-1/2',\n '[&>button]:after:w-[40%] [&>button]:after:h-[1.5px] [&>button]:after:rounded-full',\n '[&>button]:after:bg-primary',\n // today + selected:bar 切 on-emphasis(白)— 只為「藍底白字圓」的選中日/端點設計;\n // range 中段(淺灰底)由 range_middle 的 !bg-primary 覆寫回藍(2026-07-07 user 拍板)。\n '[&[data-selected=true]>button]:after:bg-on-emphasis',\n ),\n // 2026-09-07 修 M24 違反(user 抓圖:「明明都是 disabled,有些字比較深有些比較淺」):\n // outside 與 disabled 是兩個獨立 modifier,RDP 會把兩個 className 都串上同一格。\n // 兩條 utility 特異性相同 → 由 stylesheet 順序決勝,實測 `text-fg-muted`(neutral-7,45%)\n // 贏過 `text-fg-disabled`(neutral-6,25%),導致**非本月又不可選的日子反而比本月不可選的更深**。\n // 依 M24「State 顯著性 precedence:disabled > muted > emphasis」與本元件 spec.md:106\n //(「outside…比 disabled 弱:outside 仍可 hover / 可點」)、:201(disabled → text-fg-disabled(M24)),\n // outside 的淡化**只適用於還能點的日子**,故加 :not() 前提而非用 !important 硬壓。\n outside: '[&>button:not(:disabled):not([aria-disabled=\"true\"])]:text-fg-muted',\n // Selected(single 或 range 端點):button 藍底白字圓\n selected: cn(\n '[&>button]:bg-primary [&>button]:text-on-emphasis',\n '[&>button]:hover:bg-primary-hover [&>button]:hover:!ring-0',\n ),\n disabled: cn(\n '[&>button]:bg-disabled [&>button]:text-fg-disabled [&>button]:cursor-not-allowed',\n '[&>button]:hover:!ring-0 [&>button]:hover:bg-disabled',\n ),\n // ── Range track(canonical 2026-05-03 v6,Ant stadium pattern)──\n // v5 用 pseudo 矩形蓋全 cell 修「白色破圖」,但新副作用:button 圓比矩形小,4 個\n // corner triangle 區域 pseudo grey 凸出圓外(user 2026-05-03 抓到「凸出去」)。\n // 對齊 Ant 實證(`cell-range-start::before { border-radius: 9999px 0 0 9999px }`):\n // rangeStart pseudo 加 `rounded-l-full` → pseudo 變「左半圓 + 右矩形」stadium\n // 左半圓 EXACTLY OVERLAY button 圓的左半圓(同 center,同 radius 14)→ 邊界無縫\n // 右側矩形 bridge 2px to middle → 跟 middle pseudo 連續\n // Cell 的 top-left + bottom-left corner triangle:pseudo 不蓋 + button 不蓋 →\n // popover white 顯露(乾淨 breathing,跟 outside-of-range cells 一致視覺)\n range_start: cn(\n \"before:content-[''] before:absolute before:inset-y-0\",\n 'before:left-0 before:-right-[2px]',\n 'before:bg-neutral-selected before:pointer-events-none',\n 'before:rounded-l-full', // ← stadium 左半圓 matches button 圓的左半弧\n ),\n range_end: cn(\n \"before:content-[''] before:absolute before:inset-y-0\",\n 'before:-left-[2px] before:right-0',\n 'before:bg-neutral-selected before:pointer-events-none',\n 'before:rounded-r-full', // ← 鏡像\n ),\n range_middle: cn(\n \"before:content-[''] before:absolute before:inset-y-0 before:-inset-x-[2px]\",\n 'before:bg-neutral-selected before:pointer-events-none',\n // 2026-07-07 user 拍板:range 中段的 today bar 維持藍(切白只屬藍底選中日;白條在\n // neutral-selected 淺灰底上近乎隱形 = today 標記消失)。!important 確定性壓過 today 的\n // data-selected 切白規則(同權重靠 stylesheet 順序不可靠)。Ant 源碼實錘:cell-today\n // 指示 = colorPrimary,in-range 只換底色、無規則隱藏/改色 today 指示(panel.ts)。\n // 非 today 的中段日無 after content,本規則無作用 — 安全。\n '[&>button]:after:!bg-primary',\n // button 透明顯露 track。2026-07-05 對齊 RDP v9 真實行為(deep-audit A.1b):\n // range 中段日同樣掛 selected modifier(useRange rangeIncludesDate 不排除中段)→\n // 上方 selected 的 `[&>button]:hover:!ring-0` 一併壓制 hover ring(與 selected 一致\n // 的 hover 抑制),非原註解宣稱的「hover ring 仍顯示」;today bar 亦被 data-selected\n // selector 切 on-emphasis(見 spec「組合狀態」段)。\n '[&>button]:!bg-transparent [&>button]:!text-foreground',\n ),\n hidden: 'invisible',\n ...classNames,\n }}\n // ── SR label 中文 defaults(2026-07-05,RDP labels API 正門)──\n // 對齊 MUI X localeText「預設可覆寫」idiom + 2026-07-04 全庫 SR label 中文\n // canonical(commit 241676c6):DS 給中文 default,consumer 傳 labels 逐鍵覆寫(i18n)。\n // 禁在 components override 內 spread 後 hardcode aria-label — 會蓋死此覆寫通道\n // (2026-07-05 修正 07-04 的錯誤修法:中文要走 labels 正門,不是蓋 RDP 算好的值)。\n labels={{\n labelPrevious: () => '上一個月',\n labelNext: () => '下一個月',\n // 鏡射 RDP default 結構(labels/labelDayButton.js:today 前綴 / selected 後綴),文案中文化\n labelDayButton: (date, modifiers) => {\n let label = ZH_FULL_DATE.format(date)\n if (modifiers.today) label = `今天,${label}`\n if (modifiers.selected) label = `${label},已選取`\n return label\n },\n // 未傳 mode 的非互動 grid(spec「mode 無預設值」段)day cell 走 labelGridcell\n labelGridcell: (date, modifiers) => {\n let label = ZH_FULL_DATE.format(date)\n if (modifiers?.today) label = `今天,${label}`\n return label\n },\n labelGrid: (date) => ZH_MONTH_YEAR.format(date),\n labelWeekday: (date) => ZH_WEEKDAY.format(date),\n // 以下非 default 渲染面(captionLayout dropdown / showWeekNumber 由 consumer 經\n // {...props} 開啟)— 一併補齊,避免開啟後 SR 中英夾雜\n labelMonthDropdown: () => '選擇月份',\n labelYearDropdown: () => '選擇年份',\n labelWeekNumber: (weekNumber) => `第 ${weekNumber} 週`,\n labelWeekNumberHeader: () => '週數',\n ...labels,\n }}\n components={{\n // ── Prev/Next nav(canonical 2026-05-03 v9,DS 一致設計語言)──\n // User 2026-05-03 audit:「icon-only Button icon 都用 neutral-9,只有 dismiss 用 45%」\n // → chevron 不是 dismiss,**走 Button 預設 text-foreground**(neutral-9 85%),\n // 不開新 tier 自打嘴(撤銷 v6-v8 用 fg-muted override 的 mistake)。\n // RDP v9 `PreviousMonthButton / NextMonthButton` override(node_modules/react-day-picker/dist/esm/components/Nav.js)\n // ⚠️ children: _ 必丟棄(RDP 把 <Chevron> 當 children 傳 → 跟 Button startIcon 重疊變 double chevron)\n // 2026-07-05 修:此處**不** hardcode aria-label — 中文走上方 labels API 正門,\n // RDP 算好 labelPrevious()/labelNext() 後經 {...props} 流入 Button;\n // spread 後 hardcode(07-04 舊修法)= 蓋死 consumer labels 覆寫通道。\n PreviousMonthButton: ({ className, children: _children, ...props }) => (\n <Button variant=\"text\" size=\"xs\" iconOnly startIcon={ChevronLeft}\n className={className} {...props} />\n ),\n NextMonthButton: ({ className, children: _children, ...props }) => (\n <Button variant=\"text\" size=\"xs\" iconOnly startIcon={ChevronRight}\n className={className} {...props} />\n ),\n }}\n {...props}\n />\n )\n})\nDateGrid.displayName = 'DateGrid'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const dateGridMeta = {\n component: 'DateGrid',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n // 'selected' = 選中日期 / range 端點持續選中(藍底白字圓 bg-primary + range 帶 bg-neutral-selected);\n // 'active' 移除 — 全檔無 Tailwind 按壓 utility,無按壓專屬視覺態(2026-07-07 詞彙統一對抗稽核補修)。\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-neutral-selected', 'bg-on-emphasis', 'bg-primary', 'bg-primary-hover', 'bg-transparent'],\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-foreground', 'text-on-emphasis'],\n ring: ['ring-primary', 'ring-ring'],\n },\n} as const\n\nexport { DateGrid }\n"],"names":["DateGrid","className","props"],"mappings":"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"}
1
+ {"version":3,"file":"date-grid.js","sources":["../../../src/components/DateGrid/date-grid.tsx"],"sourcesContent":["/**\n * @internal — DS-internal 單元(per `packages/design-system/ds-canonical/rules/ui-development.md` Public vs Internal canonical;spec frontmatter `isInternal`)。\n * 不進 root barrel front-door;由 DatePicker wrap 消費,end-user app 請用 wrapper 元件。\n */\n// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// M22 retrofit DONE 2026-05-03 v11(real source URLs added inline at lines 38 + 111)\nimport * as React from 'react'\nimport { DayPicker } from 'react-day-picker'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport 'react-day-picker/style.css'\n\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\n\n/**\n * DateGrid — DayPicker 包裝,用本 DS token 覆寫預設視覺。\n *\n * ── 視覺對照(ref/datepicker.png,2026-04-21 rewrite)──\n *\n * | 區塊 | 規格 |\n * |------|------|\n * | Outer padding | `p-3`(12px 四邊對稱) |\n * | Nav + Month caption row | h-field-xs(24px)單行,chevron(xs)分居左右 / 月份置中垂直對齊 |\n * | Nav → Weekday gap | 12px(month_caption mb-3) |\n * | Weekday | text-body(14px)text-foreground font-medium(neutral-9,同 caption 權重;撤銷 v3 fg-secondary) |\n * | Cell gap(水平 + 垂直)| 4px(table-native border-separate border-spacing-1;非 gap — grid 會 break,見下方 v7 註) |\n * | Day cell size | h-field-sm w-[var(--field-height-sm)](28×28 md / 32×32 lg) |\n * | Day button | rounded-full 填滿 cell |\n *\n * ── 五種 cell state canonical ──\n *\n * | State | 視覺 | Token |\n * |-------|------|-------|\n * | today | 數字下方藍色短桿 | `::after` pseudo bar(bg-primary,w-40% h-1.5px rounded-full,貼近數字底)|\n * | disabled | 灰底圓圈 + disabled 字色(跟 Button disabled 一致) | [&>button]:bg-disabled [&>button]:text-fg-disabled rounded-full |\n * | outside(非本月) | 單月:text-fg-muted(neutral-7)—— **僅在該日仍可點、且未被選中時**(不可點讓位 disabled、選中讓位藍底白字,M24);**兩月以上不渲染**(numberOfMonths > 1 強制 showOutsideDays=false) | [&:not([data-selected])>button:not(:disabled):not([aria-disabled=\"true\"])]:text-fg-muted |\n * | selected / range 端點 | 藍底白字圓 | [&>button]:bg-primary [&>button]:text-on-emphasis rounded-full |\n * | range middle | 灰底矩形 track(bg-neutral-selected = neutral-2),**高度 = cell 高度**(28×28 @ md) | before pseudo: `inset-y-0 -inset-x-[2px]` |\n * | range start/end 半圓 track | 左/右半圓 + selected 圓疊在上,**圓半徑 = button 半徑** | before pseudo: `rounded-l/r-full` + start `left-0 -right-[2px]` / end `-left-[2px] right-0`(向 middle 外擴 2px bridge gap)|\n * | hover(未選中) | 藍圈 outline | hover:ring-[1.5px] hover:ring-primary-hover(2026-07-07 統一:瞬時 hover = hover 階)|\n * | range 預覽框(停留 / 焦點,只有 DatePicker.Range 會算出) | 同色同粗的藍色細框,把「點下去會變成」的區間框起來;兩端半圓、與 track 同高;停留日就是框的那一端(不再畫單格圈) | td `::after`(track 用 `::before`):`RANGE_PREVIEW_CLASSNAMES`,ring / inset 陰影 1.5px primary-hover(不用 border:1.5px 會被取整成 1px);user 2026-09-23 拍板 |\n *\n * ── Range track 高度 canonical(2026-05-03 v6,M8 4 家對照)──\n * Ant Design([picker source](https://github.com/ant-design/ant-design/blob/master/components/date-picker/style/panel.ts))/ Material X DateRangePicker([mui-x source](https://github.com/mui/mui-x/tree/master/packages/x-date-pickers-pro/src/DateRangeCalendar))/ Apple Calendar `@benchmark-unverified`(closed-source)/ Google Calendar `@benchmark-unverified`(closed-source)共識:\n * **range track 為連續 stadium**,跟 selected 圓緊貼成連續 pill。\n * 實作:bg 走 `before:` pseudo 用 `inset-y-0`(滿 cell 高度)+ `-inset-x-[2px]`\n * (左右各外擴 2px bridge 相鄰 cell gap)→ 相鄰 cell 的 pseudo 連續銜接 = 橫向連貫;\n * day cell 本身就是 button 容器(L102-105 day = h-field-sm cell),故滿 cell 高 = 滿 button 高。\n *\n * ── Range track 色用 bg-neutral-selected(= neutral-2)──\n * 對齊 TimePicker 選中項目樣式(semantic `--neutral-selected` = neutral-2,semantic.css L84/L340)。\n * 同 DS 內「持續選中」語意 token,不自開 neutral-3 tier。\n *\n * ── 為什麼 nav 放頂部 + 年月垂直置中(不 separate 兩行)──\n * ref/datepicker.png:chevron prev / 月份 / chevron next 同一行,24px 高(xs field height)。\n * 省垂直空間,使用者視線不需上下跳。世界級(Google Calendar / Apple / iOS 日期輸入)皆此佈局。\n */\n\nexport type DateGridProps = React.ComponentProps<typeof DayPicker>\n\n// ── Range 視覺的唯一住所(2026-09-23)──\n// 已選區間的灰色 track(td `::before`)與停留預覽的藍色框(td `::after`)兩組 class 只宣告在這裡。\n// DateGrid 自己的 RDP range 模式(下方 classNames.range_*)和 DatePicker.Range 自管的 modifiers\n//(modifiersClassNames)都從這裡拿 —— 先前 track 的 stadium class 在兩個檔各抄一份,是假 SSOT(M17)。\n//\n// 幾何(spec「Range track canonical」+「區間預覽框」):\n// - inset-y-0 = 滿 cell 高(= button 高,28 @ md);左右各外擴 2px 接鄰格的 border-spacing 縫\n// - 起點 / 終點只在朝外那一側畫側邊與半圓(rounded-l/r-full,圓半徑 = button 半徑,和藍圓同弧)\n// - 列首 / 列尾不畫側邊(換列處框是開口的,與 track 同款;month_grid 的 first/last-child 規則把外擴歸零)\n// - 單格(起訖同一天)= 完整一圈\n// - 停留日不再畫 button 的 hover 圈:框的半圓端點就是它(user 2026-09-23:「所 hover 的日期的藍框不會是完整的\n// 圓形,而會是一個半圓,至於這個半圓的缺口朝向哪一邊則取決於正在選的是起始日還是結束日」)\nexport const RANGE_TRACK_CLASSNAMES = Object.freeze({\n start: cn(\n \"before:content-[''] before:absolute before:inset-y-0\",\n 'before:left-0 before:-right-[2px]',\n 'before:bg-neutral-selected before:pointer-events-none',\n 'before:rounded-l-full', // ← stadium 左半圓 matches button 圓的左半弧\n ),\n middle: cn(\n \"before:content-[''] before:absolute before:inset-y-0 before:-inset-x-[2px]\",\n 'before:bg-neutral-selected before:pointer-events-none',\n ),\n end: cn(\n \"before:content-[''] before:absolute before:inset-y-0\",\n 'before:-left-[2px] before:right-0',\n 'before:bg-neutral-selected before:pointer-events-none',\n 'before:rounded-r-full', // ← 鏡像\n ),\n})\n\n// 框的顏色與粗細逐字沿用單日 hover 圈(day_button 的 hover:ring-[1.5px] hover:ring-primary-hover):同一套語言,\n// 不新增第二種「暫定」表達(user 2026-09-23 Q2 拍板選實線,不用 Ant v4 / MUI 的虛線)。\n// **用 ring(box-shadow)畫、不用 border**:瀏覽器把 border-width 1.5px 在 DPR 1 取整成 1px(閘實測 computed 1px),\n// box-shadow 不取整 —— 只有這樣才跟 hover 圈同一種筆觸(同色、同粗、同反鋸齒),不是「看起來差不多」。\nconst PREVIEW_FRAME = cn(\n \"after:content-[''] after:absolute after:inset-y-0 after:pointer-events-none\",\n // 停留日的單格圈讓位給框的端點(preview 的四種格都可能是停留日)\n '[&>button]:hover:!ring-0',\n)\n// 端點與單格:整圈內描邊(沿弧線等粗)。端點再把朝區間內側那 2px(外擴接縫區)裁到只剩上下兩條 1.5px 帶,\n// 直邊消失、半圓的缺口就朝向區間(user Q5)。clip-path 裡的 2px = 外擴量,1.5px = 筆觸寬 —— 兩個數字都不是獨立的。\nconst PREVIEW_STROKE = 'after:ring-inset after:ring-[1.5px] after:ring-primary-hover'\nconst CLIP_INNER_RIGHT = 'after:[clip-path:polygon(0_0,100%_0,100%_1.5px,calc(100%_-_2px)_1.5px,calc(100%_-_2px)_calc(100%_-_1.5px),100%_calc(100%_-_1.5px),100%_100%,0_100%)]'\nconst CLIP_INNER_LEFT = 'after:[clip-path:polygon(0_0,100%_0,100%_100%,0_100%,0_calc(100%_-_1.5px),2px_calc(100%_-_1.5px),2px_1.5px,0_1.5px)]'\nexport const RANGE_PREVIEW_CLASSNAMES = Object.freeze({\n start: cn(PREVIEW_FRAME, PREVIEW_STROKE, 'after:left-0 after:-right-[2px] after:rounded-l-full', CLIP_INNER_RIGHT),\n // 中段只有上下兩條線:兩個各 1.5px 的 inset 陰影(ring 畫不了單邊)\n middle: cn(PREVIEW_FRAME, 'after:-inset-x-[2px] after:shadow-[inset_0_1.5px_0_0_var(--primary-hover),inset_0_-1.5px_0_0_var(--primary-hover)]'),\n end: cn(PREVIEW_FRAME, PREVIEW_STROKE, 'after:-left-[2px] after:right-0 after:rounded-r-full', CLIP_INNER_LEFT),\n single: cn(PREVIEW_FRAME, PREVIEW_STROKE, 'after:inset-x-0 after:rounded-full'),\n})\n// 預覽「上膛」的格(停留會有框的那些天)**靜態**壓掉 button 的單格 hover 圈。\n// 單格圈是 CSS :hover,指標一到就畫;框是 React 狀態,慢一幀。只靠上面 PREVIEW_FRAME 裡的壓制(掛在停留後才出現的\n// preview modifier 上),每次停留都會先閃一圈整圓、再變成半圓(user 2026-09-23:「hover 到日期都會先看到一圈圓形藍色外框,\n// 閃了一下,才會變成半圓」)。壓制不能依賴慢的那一邊 —— 由 consumer 用「這一天停留會不會有框」當 modifier,停留前就掛好;\n// 順序不合的日子(不會有框)與對面那端還空時(沒東西可接)不掛,單格圈照畫。\nexport const RANGE_PREVIEW_ARMED_CLASSNAME = '[&>button]:hover:!ring-0'\n// 藍底格(選中日 / 區間端點)上的鍵盤焦點:1px 白線退 3px(styles/base.css `focus-ring-inset-emphasis`;\n// focus-canonical「填色元素上的內描邊」,2026-09-23 user 拍板 D)。`[&>button]:focus-visible:` 的特異性高過\n// day_button 自己的 `focus-visible:focus-ring-inset`,藍底格由這條勝出。\nexport const EMPHASIS_FOCUS_RING_CLASSNAME = '[&>button]:focus-visible:focus-ring-inset-emphasis'\n\n// ── SR label 中文 formatter(2026-07-05)──\n// 走 Intl.DateTimeFormat(對齊 TimePicker / Calendar 既有 canonical,不引第二套 date lib);\n// module-level 建一次 — label fn 每個 day cell 都會呼叫,避免 per-call new formatter。\nconst ZH_FULL_DATE = new Intl.DateTimeFormat('zh-TW', { dateStyle: 'full' })\nconst ZH_MONTH_YEAR = new Intl.DateTimeFormat('zh-TW', { year: 'numeric', month: 'long' })\nconst ZH_WEEKDAY = new Intl.DateTimeFormat('zh-TW', { weekday: 'long' })\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGrid(\n {\n className,\n classNames,\n labels,\n showOutsideDays = true,\n numberOfMonths,\n ...props\n },\n _ref,\n) {\n // Note: react-day-picker v9 DayPicker 未對外 forward ref 到單一 DOM 節點(內部有多 div),\n // 故 ref 簽名保留但不附著(符合 DS 統一 forwardRef 慣例;真要取 DOM 用 wrapper 包)。\n return (\n <DayPicker\n // 兩月以上**不渲染鄰月日子**(2026-09-23 user 拍板):同一天會在相鄰兩張月曆各出現一次,區間 track / 端點藍圓 /\n // 預覽框就被畫兩次(user 圖一:4/26 在四月與五月面板各一顆藍圓)。MUI X(calendars > 1 時 filler 格 opacity 0,\n // 原註解「otherwise the same day would be rendered in two calendars」)/ Polaris(空格)/ flatpickr(hidden)與\n // react-day-picker 自家預設都不顯示;consumer 傳 showOutsideDays 也不放行(MUI 同樣忽略)。單月照舊(預設顯示、淡字)。\n // 一條原則(2026-09-24 user 拍板):鄰月日子只在「同一天不會被畫兩次」時顯示;八家對照 → date-grid.spec.md「鄰月日子:一條原則」。\n showOutsideDays={(numberOfMonths ?? 1) > 1 ? false : showOutsideDays}\n numberOfMonths={numberOfMonths}\n // navLayout=\"around\" = prev 渲染在首月(displayIndex===0)caption 左、next 渲染在末月(displayIndex===numberOfMonths-1)caption 右;單月時兩鍵同 caption 兩側\n // 取代先前 absolute 定位覆蓋整個 months 容器導致箭頭垂直置中於中段的 bug\n navLayout=\"around\"\n // 根**不帶內距**:內距搬到每一張月曆自己身上(見下方 `month`)。\n // 2026-09-18 user 裁示,原話:「我覺得真的要做的話,邏輯就是這樣,而不是另外加一個 token,\n // 反而造成漂移,因為視覺就是要在各種情況營造對稱感吧?」\n className={cn(className)}\n classNames={{\n // **兩張月曆之間不設 gap**(2026-09-18 user 裁示)。\n // 每張月曆自己四周留 `--item-px`,所以彼此之間自然就是**兩份內距**(md 12+12 = 24),\n // 而且「單張」與「並排」看起來完全一樣 —— 對稱是結構保證的,不是靠一個要人維護的數字。\n // 這也是為什麼**不需要**月間距 token:少一個可以漂的東西。\n // 世界級同款:Ant Design 就是這個作法(兩張各自帶左右內距、中間不設 gap),\n // 且 v4(內距 12 → 間距 24)與 v5(18 → 36)跨大版本維持 1:2,是刻意的比例。\n months: 'flex flex-col sm:flex-row',\n // Month:relative 讓 prev/next 按鈕 absolute 定位到 month 右上/左上(navLayout=\"around\")。\n // **內距住在這裡**(2026-09-18 搬家):每張月曆自己四周留 `--item-px`,\n // 於是「面板邊 → 第一格」與「月曆 → 月曆」用的是同一顆東西,後者自然是前者的兩份。\n month: 'flex flex-col relative p-[var(--item-px,var(--field-px))]',\n // Month caption:單行置中 h-field-xs,prev/next 按鈕 absolute 從兩側貼齊\n month_caption: 'flex items-center justify-center h-field-xs mb-3',\n caption_label: 'text-body font-medium',\n // ── Prev/Next button(canonical 2026-05-03 v6,user audit fix)──\n // RDP 把這 className apply 到 <Button> 本身(不是 wrapper),所以用 className 直接套\n // 定位類(absolute top/left/right-0)。muted 色透過 Button override 內部加,不用 [&>button] 黑魔法。\n // nav 的 absolute 定位是相對 month 的 **padding box**(= 盒子最外緣),\n // 內距搬到 month 之後不補這個偏移,chevron 會貼到月曆盒最外緣、比日期格往外 12px(2026-09-18 實測確認)。\n // 讀同一顆 `--item-px`,所以它跟日期格永遠在同一條線上。\n button_previous: 'absolute top-[var(--item-px,var(--field-px))] left-[var(--item-px,var(--field-px))] z-[1]',\n button_next: 'absolute top-[var(--item-px,var(--field-px))] right-[var(--item-px,var(--field-px))] z-[1]',\n // ── Grid layout(canonical 2026-05-03 v7,純 table-native)──\n // RDP v9 month_grid = <table>。v6 試 grid 在 tr 上但 break border-spacing(grid 蓋掉\n // table-row layout)。乾淨修:**純 table layout** + `border-spacing-1`(4px H+V,table-native)。\n // 所有 cells 自動同寬同高(td 的 w/h-field-sm),無 grid hack。\n //\n // `-m-1` 抵銷**最外圈**的 border-spacing(2026-09-18 user 拍板)。\n // 為什麼需要它:`border-spacing` 是「格與格之間 4px」,但 CSS 連**最外圈也各給 4px** ——\n // 於是日期格的盒子落在 容器 12 + 4 = 16,而同一個面板的上下月 chevron 貼著容器的 12。\n // 規格(本檔 spec「Spacing canonical」原文,2026-05-03 v8)寫的是\n //「四邊對稱:chevron 按鈕到邊距 = 最左最右日期 cell 到邊距(12px)」—— 兩者都該是 12。\n // 那 4px 外圈**從來沒有人決定過**(全 repo 只有 2026-09-18 的 spec 段落提到它),是 `border-separate` 的副作用。\n // 負 margin 把 table 的盒子往外拉 4px:**格與格之間仍然是 4px,但最外圈歸零**,第一格因此落在 12。\n // 對照上游:我們包的 `react-day-picker` 自己是 `border-collapse: collapse`,\n // 它 457 行的 style.css 裡 `border-spacing` 出現 **0 次**(2026-09-18 讀 node_modules 原始碼);\n // IBM Carbon 的日曆同樣是第一格貼齊容器內距(實測外溢 0)。外溢是我們自己加的,不是慣例。\n // 列頭尾的 bridge 夾住:`-2px` 是用來跨過格與格之間的 4px 縫去接鄰格,\n // 但**列的第一格左邊、最後一格右邊沒有鄰格**,再往外就越過容器內距 —— 抵銷最外圈之後會直接溢出面板留白(實測 2px)。\n // 掛在 month_grid 而不是三個 range 狀態各寫一次:2026-09-18 實測 react-day-picker **不會**把\n // 我們加在 `classNames.range_*` 裡的這兩個 class 帶到 `<td>` 上(bundle 有、`cn()` 不吃、chunk 也載對了,\n // 但 DOM 完全查不到),而 `month_grid` 的 class 確定會落地(同一行的 `-m-1` 就是證據)。\n // 一處宣告、三種 range 狀態一起管,也少兩個要同步的地方。\n month_grid: cn(\n 'border-separate border-spacing-1 -m-1',\n '[&_tr>td:first-child]:before:!left-0',\n '[&_tr>td:last-child]:before:!right-0',\n // 預覽框(td ::after)在列首 / 列尾同樣不得溢出面板留白;換列處框是開口的,與 track 同款\n '[&_tr>td:first-child]:after:!left-0',\n '[&_tr>td:last-child]:after:!right-0',\n ),\n weekdays: '', // thead default\n weekday: cn(\n // text-foreground + font-medium 對齊 DS 一致設計語言(2026-05-03 user audit):\n // weekday 列標跟 caption「April 2026」同視覺權重(都屬 calendar header 區),\n // 不弱化(撤銷 v3 fg-secondary 的 mistake)。對齊 icon-only Button 預設 neutral-9 一致。\n 'text-foreground text-body font-medium',\n // h-field-sm(md=28/lg=32 隨 density):對齊 day cell 同 token 同 row cadence\n // (spec:127 既定 canonical;Ant 36/36、Carbon 40/40 header/day 同節奏;2026-07-14 修 h-7 固定 drift)\n 'h-[var(--field-height-sm)] align-middle text-center',\n ),\n week: '', // tr default\n // Cell **就是** button 容器(28×28 @ md / 32×32 @ lg),`relative` 讓 before pseudo 定位\n // ── h-field-sm 對齊 user spec(28×28 @ md)— v3 用 h-field-md (32×32) 是錯的\n day: cn(\n 'h-field-sm w-[var(--field-height-sm)] p-0 text-center relative',\n ),\n day_button: cn(\n // absolute inset-0 = 完全填滿 cell(naked button,無 inset 4px 空隙)\n // z-[1] 讓 button 疊在 range track `before:` pseudo 之上\n 'absolute inset-0 z-[1] flex items-center justify-center',\n // hover 底色瞬間切換,不做過渡(user 2026-09-10 拍板「第三題改成全部瞬間」;SSOT = tokens/motion/motion.spec.md「hover 回饋不做過渡」)\n 'font-normal text-body rounded-full',\n // Hover 藍圈 1.5px(對齊 Apple HIG / Ant `@benchmark-unverified` visual ring measurement)— ring 在 button 之上 + 透明 bg 不擋 range track\n // 2026-07-07 user 拍板統一:瞬時 hover 進 primary 家族 = hover 階(FileUpload dropzone /\n // Slider thumb hover 同族;base 專屬持續選中與 focus)——ring-primary → ring-primary-hover\n 'hover:ring-[1.5px] hover:ring-primary-hover hover:bg-transparent',\n // 鍵盤焦點框往內畫(focus-canonical「問題二」):格與格只隔 4px,而區間 track(::before)與預覽框(::after)\n // 就跑在那 4px 縫裡 —— 往外畫的 2px 框正好壓在框線上(user 2026-09-23:「date 的鍵盤焦點感覺要改成\n // 往內畫的那種,否則會跟區間藍框有視覺衝突」)。藍底格另走 EMPHASIS_FOCUS_RING_CLASSNAME(白線退 3px)。\n 'focus-visible:focus-ring-inset',\n // 命中區外擴 2px 補滿格間 4px 縫隙(user 2026-09-23:「從某日水平移動到其隔日,藍色的區間框線都會閃動一下」):\n // 停留日掛在 button 的 mouseenter / mouseleave(react-day-picker 只給這一層),指標跨格時只要有一次 mousemove 落在\n // border-spacing 的縫裡就先 leave 再 enter,整條框先卸掉一幀再補回。框的畫法早就用 −2px 跨過縫隙把線接起來,\n // 命中幾何卻沒跟上 —— 這一條讓 hit 與 paint 同一個數字。不畫任何東西(基準圖不動);Ant 整個 td 可命中、\n // MUI 只在離開整個日曆才清,都沒有「縫隙裡沒有任何一天」這個狀態。閘:datepicker-range-preview.mjs「跨格不閃」。\n \"before:content-[''] before:absolute before:-inset-[2px]\",\n ),\n // today:藍色 underline bar 貼近數字\n today: cn(\n \"[&>button]:after:content-['']\",\n '[&>button]:after:absolute',\n '[&>button]:after:bottom-[5px] [&>button]:after:left-1/2 [&>button]:after:-translate-x-1/2',\n '[&>button]:after:w-[40%] [&>button]:after:h-[1.5px] [&>button]:after:rounded-full',\n '[&>button]:after:bg-primary',\n // today + selected:bar 切 on-emphasis(白)— 只為「藍底白字圓」的選中日/端點設計;\n // range 中段(淺灰底)由 range_middle 的 !bg-primary 覆寫回藍(2026-07-07 user 拍板)。\n '[&[data-selected=true]>button]:after:bg-on-emphasis',\n ),\n // 2026-09-07 修 M24 違反(user 抓圖:「明明都是 disabled,有些字比較深有些比較淺」):\n // outside 與 disabled 是兩個獨立 modifier,RDP 會把兩個 className 都串上同一格。\n // 兩條 utility 特異性相同 → 由 stylesheet 順序決勝,實測 `text-fg-muted`(neutral-7,45%)\n // 贏過 `text-fg-disabled`(neutral-6,25%),導致**非本月又不可選的日子反而比本月不可選的更深**。\n // 依 M24「State 顯著性 precedence:disabled > muted > emphasis」與本元件 spec.md:106\n //(「outside…比 disabled 弱:outside 仍可 hover / 可點」)、:201(disabled → text-fg-disabled(M24)),\n // outside 的淡化**只適用於還能點的日子**,故加 :not() 前提而非用 !important 硬壓。\n // 2026-09-23 再加 `:not([data-selected])`:淡字是裝飾、選中是 state(M24 state 勝裝飾)—— 單月裡選中日落在鄰月位置時\n // 仍是藍底白字圓,不能被淡字選擇器(特異性 (0,3,1))壓成灰字。RDP `mode=\"range\"` 單月的鄰月中段日也掛 selected,\n // 字色因此跟當月中段一樣深(不淡化);兩月時鄰月日子根本不渲染,這條只在單月 range 出現。\n outside: '[&:not([data-selected])>button:not(:disabled):not([aria-disabled=\"true\"])]:text-fg-muted',\n // Selected(single 或 range 端點):button 藍底白字圓\n selected: cn(\n '[&>button]:bg-primary [&>button]:text-on-emphasis',\n '[&>button]:hover:bg-primary-hover [&>button]:hover:!ring-0',\n EMPHASIS_FOCUS_RING_CLASSNAME,\n ),\n disabled: cn(\n '[&>button]:bg-disabled [&>button]:text-fg-disabled [&>button]:cursor-not-allowed',\n '[&>button]:hover:!ring-0 [&>button]:hover:bg-disabled',\n ),\n // ── Range track(canonical 2026-05-03 v6,Ant stadium pattern)──\n // v5 用 pseudo 矩形蓋全 cell 修「白色破圖」,但新副作用:button 圓比矩形小,4 個\n // corner triangle 區域 pseudo grey 凸出圓外(user 2026-05-03 抓到「凸出去」)。\n // 對齊 Ant 實證(`cell-range-start::before { border-radius: 9999px 0 0 9999px }`):\n // rangeStart pseudo 加 `rounded-l-full` → pseudo 變「左半圓 + 右矩形」stadium\n // 左半圓 EXACTLY OVERLAY button 圓的左半圓(同 center,同 radius 14)→ 邊界無縫\n // 右側矩形 bridge 2px to middle → 跟 middle pseudo 連續\n // Cell 的 top-left + bottom-left corner triangle:pseudo 不蓋 + button 不蓋 →\n // popover white 顯露(乾淨 breathing,跟 outside-of-range cells 一致視覺)\n // class 本體住在檔頭 RANGE_TRACK_CLASSNAMES(DatePicker.Range 消費同一份)\n range_start: RANGE_TRACK_CLASSNAMES.start,\n range_end: RANGE_TRACK_CLASSNAMES.end,\n range_middle: cn(\n RANGE_TRACK_CLASSNAMES.middle,\n // 2026-07-07 user 拍板:range 中段的 today bar 維持藍(切白只屬藍底選中日;白條在\n // neutral-selected 淺灰底上近乎隱形 = today 標記消失)。!important 確定性壓過 today 的\n // data-selected 切白規則(同權重靠 stylesheet 順序不可靠)。Ant 源碼實錘:cell-today\n // 指示 = colorPrimary,in-range 只換底色、無規則隱藏/改色 today 指示(panel.ts)。\n // 非 today 的中段日無 after content,本規則無作用 — 安全。\n '[&>button]:after:!bg-primary',\n // button 透明顯露 track。2026-07-05 對齊 RDP v9 真實行為(deep-audit A.1b):\n // range 中段日同樣掛 selected modifier(useRange rangeIncludesDate 不排除中段)→\n // 上方 selected 的 `[&>button]:hover:!ring-0` 一併壓制 hover ring(與 selected 一致\n // 的 hover 抑制),非原註解宣稱的「hover ring 仍顯示」;today bar 亦被 data-selected\n // selector 切 on-emphasis(見 spec「組合狀態」段)。\n '[&>button]:!bg-transparent [&>button]:!text-foreground',\n // 中段日同樣掛 selected(見上),會一併吃到 selected 的白線焦點 —— 但中段底是淺灰、白線看不見,\n // 焦點要回到一般的往內 2px 藍線;同權重靠順序不可靠,用 ! 壓過。\n '[&>button]:focus-visible:!focus-ring-inset',\n ),\n hidden: 'invisible',\n ...classNames,\n }}\n // ── SR label 中文 defaults(2026-07-05,RDP labels API 正門)──\n // 對齊 MUI X localeText「預設可覆寫」idiom + 2026-07-04 全庫 SR label 中文\n // canonical(commit 241676c6):DS 給中文 default,consumer 傳 labels 逐鍵覆寫(i18n)。\n // 禁在 components override 內 spread 後 hardcode aria-label — 會蓋死此覆寫通道\n // (2026-07-05 修正 07-04 的錯誤修法:中文要走 labels 正門,不是蓋 RDP 算好的值)。\n labels={{\n labelPrevious: () => '上一個月', // i18n-allow: DS default; consumer override via labels prop\n labelNext: () => '下一個月', // i18n-allow: DS default; consumer override via labels prop\n // 鏡射 RDP default 結構(labels/labelDayButton.js:today 前綴 / selected 後綴),文案中文化\n labelDayButton: (date, modifiers) => {\n let label = ZH_FULL_DATE.format(date)\n if (modifiers.today) label = `今天,${label}`\n if (modifiers.selected) label = `${label},已選取`\n return label\n },\n // 未傳 mode 的非互動 grid(spec「mode 無預設值」段)day cell 走 labelGridcell\n labelGridcell: (date, modifiers) => {\n let label = ZH_FULL_DATE.format(date)\n if (modifiers?.today) label = `今天,${label}`\n return label\n },\n labelGrid: (date) => ZH_MONTH_YEAR.format(date),\n labelWeekday: (date) => ZH_WEEKDAY.format(date),\n // 以下非 default 渲染面(captionLayout dropdown / showWeekNumber 由 consumer 經\n // {...props} 開啟)— 一併補齊,避免開啟後 SR 中英夾雜\n labelMonthDropdown: () => '選擇月份', // i18n-allow: DS default; consumer override via labels prop\n labelYearDropdown: () => '選擇年份', // i18n-allow: DS default; consumer override via labels prop\n labelWeekNumber: (weekNumber) => `第 ${weekNumber} 週`,\n labelWeekNumberHeader: () => '週數',\n ...labels,\n }}\n components={{\n // ── Prev/Next nav(canonical 2026-05-03 v9,DS 一致設計語言)──\n // User 2026-05-03 audit:「icon-only Button icon 都用 neutral-9,只有 dismiss 用 45%」\n // → chevron 不是 dismiss,**走 Button 預設 text-foreground**(neutral-9 85%),\n // 不開新 tier 自打嘴(撤銷 v6-v8 用 fg-muted override 的 mistake)。\n // RDP v9 `PreviousMonthButton / NextMonthButton` override(node_modules/react-day-picker/dist/esm/components/Nav.js)\n // ⚠️ children: _ 必丟棄(RDP 把 <Chevron> 當 children 傳 → 跟 Button startIcon 重疊變 double chevron)\n // 2026-07-05 修:此處**不** hardcode aria-label — 中文走上方 labels API 正門,\n // RDP 算好 labelPrevious()/labelNext() 後經 {...props} 流入 Button;\n // spread 後 hardcode(07-04 舊修法)= 蓋死 consumer labels 覆寫通道。\n PreviousMonthButton: ({ className, children: _children, ...props }) => (\n <Button variant=\"text\" size=\"xs\" iconOnly startIcon={ChevronLeft}\n className={className} {...props} />\n ),\n NextMonthButton: ({ className, children: _children, ...props }) => (\n <Button variant=\"text\" size=\"xs\" iconOnly startIcon={ChevronRight}\n className={className} {...props} />\n ),\n }}\n {...props}\n />\n )\n})\nDateGrid.displayName = 'DateGrid'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const dateGridMeta = {\n component: 'DateGrid',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n // 'selected' = 選中日期 / range 端點持續選中(藍底白字圓 bg-primary + range 帶 bg-neutral-selected);\n // 'active' 移除 — 全檔無 Tailwind 按壓 utility,無按壓專屬視覺態(2026-07-07 詞彙統一對抗稽核補修)。\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-neutral-selected', 'bg-on-emphasis', 'bg-primary', 'bg-primary-hover', 'bg-transparent'],\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-foreground', 'text-on-emphasis'],\n ring: ['ring-primary', 'ring-ring'],\n },\n} as const\n\nexport { DateGrid }\n"],"names":["DateGrid","className","props"],"mappings":"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"}
@@ -1,6 +1,10 @@
1
- import { DateGrid, dateGridMeta } from "./date-grid.js";
1
+ import { DateGrid, EMPHASIS_FOCUS_RING_CLASSNAME, RANGE_PREVIEW_ARMED_CLASSNAME, RANGE_PREVIEW_CLASSNAMES, RANGE_TRACK_CLASSNAMES, dateGridMeta } from "./date-grid.js";
2
2
  export {
3
3
  DateGrid,
4
+ EMPHASIS_FOCUS_RING_CLASSNAME,
5
+ RANGE_PREVIEW_ARMED_CLASSNAME,
6
+ RANGE_PREVIEW_CLASSNAMES,
7
+ RANGE_TRACK_CLASSNAMES,
4
8
  dateGridMeta
5
9
  };
6
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"date-picker.d.ts","sourceRoot":"","sources":["../../../src/components/DatePicker/date-picker.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"date-picker.d.ts","sourceRoot":"","sources":["../../../src/components/DatePicker/date-picker.tsx"],"names":[],"mappings":"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"}
@@ -9,7 +9,8 @@ import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../Popov
9
9
  import { Tooltip, TooltipTrigger, TooltipContent } from "../Tooltip/tooltip.js";
10
10
  import { useTruncated } from "../../hooks/use-truncated.js";
11
11
  import { TruncatedText } from "../../patterns/element-anatomy/truncated-text.js";
12
- import { DateGrid } from "../DateGrid/date-grid.js";
12
+ import { DateGrid, RANGE_PREVIEW_CLASSNAMES, RANGE_PREVIEW_ARMED_CLASSNAME, RANGE_TRACK_CLASSNAMES, EMPHASIS_FOCUS_RING_CLASSNAME } from "../DateGrid/date-grid.js";
13
+ import { computeRangePreview, rangePreviewModifiers } from "./range-preview.js";
13
14
  import { Button } from "../Button/button.js";
14
15
  import { SurfaceFooter } from "../../patterns/overlay-surface/overlay-surface.js";
15
16
  import { useFieldContext, useResolvedFieldSize, useResolvedFieldInvalid, useResolvedFieldDisabled, useResolvedFieldMode, useFieldEmptyDisplay, useResolvedFieldVariant, fieldEmptyColorClass } from "../Field/field-context.js";
@@ -571,6 +572,26 @@ const DatePickerRange = React.forwardRef(
571
572
  }
572
573
  return mods;
573
574
  }, [startDate, endDate]);
575
+ const [previewAnchor, setPreviewAnchor] = React.useState(null);
576
+ const keyboardDayRef = React.useRef(null);
577
+ React.useEffect(() => {
578
+ if (!open) {
579
+ setPreviewAnchor(null);
580
+ keyboardDayRef.current = null;
581
+ }
582
+ }, [open]);
583
+ const previewFor = React.useCallback(
584
+ (anchor) => computeRangePreview({ activeEnd, start: startDate, end: endDate, anchor }),
585
+ [activeEnd, startDate, endDate]
586
+ );
587
+ const previewModifiers = React.useMemo(
588
+ () => ({
589
+ // 上膛:這一天停留會有框 → 停留前就靜態壓掉它的單格 hover 圈(理由見 DateGrid RANGE_PREVIEW_ARMED_CLASSNAME)
590
+ previewArmed: (day) => previewFor(day) !== null,
591
+ ...rangePreviewModifiers(previewFor((previewAnchor == null ? void 0 : previewAnchor.day) ?? null))
592
+ }),
593
+ [previewFor, previewAnchor]
594
+ );
574
595
  const commitRange = (next) => {
575
596
  if (needConfirm) setDraft(next);
576
597
  else {
@@ -779,7 +800,7 @@ const DatePickerRange = React.forwardRef(
779
800
  }
780
801
  }
781
802
  },
782
- modifiers: showTime ? {} : rangeModifiers,
803
+ modifiers: { ...showTime ? {} : rangeModifiers, ...previewModifiers },
783
804
  modifiersClassNames: {
784
805
  // ── Range visual canonical(2026-05-03 v8 stadium pattern)──
785
806
  // v5 修「白色破圖」用 pseudo 蓋全 cell 矩形,但新副作用:button 圓比矩形小,
@@ -792,30 +813,48 @@ const DatePickerRange = React.forwardRef(
792
813
  // popover white 顯露(乾淨 breathing)
793
814
  rangeStart: cn(
794
815
  // 2026-08-11 修偏移:!bg-primary 連 hover 也壓死 → 端點 hover 零回饋,違反
795
- // date-grid.spec.md:119「選中日 hover 升階 primary→primary-hover」(端點可再點
816
+ // date-grid.spec.md:121「選中日 hover 升階 primary→primary-hover」(端點可再點
796
817
  // 重選範圍 = 可取消家族,依 user 原則該有回饋)。補顯式 hover 升階(同 !important,
797
- // hover 變體更特定故 hover 時勝出;ring-0 壓制照舊,spec:118)。
818
+ // hover 變體更特定故 hover 時勝出;ring-0 壓制照舊,spec:120)。
798
819
  "[&>button]:!bg-primary [&>button]:hover:!bg-primary-hover [&>button]:!text-on-emphasis [&>button]:hover:!ring-0",
799
- "before:content-[''] before:absolute before:inset-y-0",
800
- "before:left-0 before:-right-[2px]",
801
- "before:bg-neutral-selected before:pointer-events-none",
802
- "before:rounded-l-full"
803
- // ← stadium 左半圓 matches button 圓的左半弧
820
+ // 藍底上的鍵盤焦點 = 1px 白線退 3px(同 DateGrid selected;端點不掛 RDP selected,要自己帶)
821
+ EMPHASIS_FOCUS_RING_CLASSNAME,
822
+ // track 的 stadium class 只住在 DateGrid(2026-09-23 收斂;先前這裡各抄一份 = 假 SSOT)
823
+ RANGE_TRACK_CLASSNAMES.start
804
824
  ),
805
825
  rangeEnd: cn(
806
- // 2026-08-11:同 rangeStart — 補 hover 升階(spec:119),鏡像。
826
+ // 2026-08-11:同 rangeStart — 補 hover 升階(spec:121),鏡像。
807
827
  "[&>button]:!bg-primary [&>button]:hover:!bg-primary-hover [&>button]:!text-on-emphasis [&>button]:hover:!ring-0",
808
- "before:content-[''] before:absolute before:inset-y-0",
809
- "before:-left-[2px] before:right-0",
810
- "before:bg-neutral-selected before:pointer-events-none",
811
- "before:rounded-r-full"
812
- // ← 鏡像
828
+ EMPHASIS_FOCUS_RING_CLASSNAME,
829
+ RANGE_TRACK_CLASSNAMES.end
813
830
  ),
814
831
  rangeMiddle: cn(
815
- "before:content-[''] before:absolute before:inset-y-0 before:-inset-x-[2px]",
816
- "before:bg-neutral-selected before:pointer-events-none",
817
- "[&>button]:!bg-transparent [&>button]:!text-foreground"
818
- )
832
+ RANGE_TRACK_CLASSNAMES.middle,
833
+ // 2026-09-23 拿掉 `!text-foreground`:它是 2026-04-21 為了壓過 RDP range 模式中段自帶的 selected 白字而加,
834
+ // 2026-05-03 改成 mode="single" 自管區間時原封抄來 —— 這條路徑的中段不掛 selected,!important 沒有任務,
835
+ // 卻讓鄰月淡字永遠輸給它(全 repo 從沒定義過 outside × range;兩月時鄰月現已不渲染)。字色回到 day_button 預設。
836
+ "[&>button]:!bg-transparent"
837
+ ),
838
+ // ── 停留預覽框(2026-09-23 user 拍板)── 四種格由 range-preview.ts 算出,畫法住在 DateGrid
839
+ previewArmed: RANGE_PREVIEW_ARMED_CLASSNAME,
840
+ previewSingle: RANGE_PREVIEW_CLASSNAMES.single,
841
+ previewStart: RANGE_PREVIEW_CLASSNAMES.start,
842
+ previewMiddle: RANGE_PREVIEW_CLASSNAMES.middle,
843
+ previewEnd: RANGE_PREVIEW_CLASSNAMES.end
844
+ },
845
+ onDayMouseEnter: (day) => setPreviewAnchor({ day, source: "pointer" }),
846
+ onDayMouseLeave: () => setPreviewAnchor((prev) => {
847
+ if ((prev == null ? void 0 : prev.source) !== "pointer") return prev;
848
+ return keyboardDayRef.current ? { day: keyboardDayRef.current, source: "keyboard" } : null;
849
+ }),
850
+ onDayFocus: (day, _modifiers, event) => {
851
+ if (!event.currentTarget.matches(":focus-visible")) return;
852
+ keyboardDayRef.current = day;
853
+ setPreviewAnchor({ day, source: "keyboard" });
854
+ },
855
+ onDayBlur: () => {
856
+ keyboardDayRef.current = null;
857
+ setPreviewAnchor((prev) => (prev == null ? void 0 : prev.source) === "keyboard" ? null : prev);
819
858
  },
820
859
  disabled: isOutOfRangeOrder,
821
860
  numberOfMonths: showTime ? 1 : 2,