@godxjp/ui 28.3.1 → 28.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.
@@ -120,6 +120,7 @@ const ScrollArea = React.forwardRef(
120
120
  anchorOffset,
121
121
  onAnchoredChange,
122
122
  orientation = "vertical",
123
+ scrollbar = "auto",
123
124
  ...props
124
125
  }, ref) => {
125
126
  const [viewport, setViewport] = React.useState(null);
@@ -153,6 +154,7 @@ const ScrollArea = React.forwardRef(
153
154
  "data-slot": "scroll-area-viewport",
154
155
  "data-anchor": anchor,
155
156
  "data-orientation": orientation,
157
+ "data-scrollbar": scrollbar,
156
158
  className: cn("ui-scroll-area", className),
157
159
  ...props,
158
160
  children: /* @__PURE__ */ jsx("div", { "data-slot": "scroll-area-content", className: "ui-scroll-area-content", children })
@@ -1,4 +1,9 @@
1
1
  import * as React from "react";
2
+ import { DayButton, dateMatchModifiers } from "react-day-picker";
3
+ import type { DateRange, Modifiers } from "react-day-picker";
4
+ export { DayButton, dateMatchModifiers };
5
+ export type { DateRange, Modifiers };
6
+ export type { DayPickerProps, DayButtonProps, Matcher, CalendarDay, DateLib, } from "react-day-picker";
2
7
  import type { CalendarProp } from "../../props/components/data-entry.prop.js";
3
8
  export type { CalendarProp, CalendarProp as CalendarProps, CalendarCellRenderProp, CalendarFooterProp, } from "../../props/components/data-entry.prop.js";
4
9
  export declare function Calendar({ className, classNames, showOutsideDays, "aria-label": ariaLabel, labels, showToday, showClose, onClose, footer, width, bordered, cellRender, month: monthProp, onMonthChange, locale: localeProp, ...props }: CalendarProp): React.JSX.Element;
@@ -187,7 +187,9 @@ function Calendar({
187
187
  * So the original node is passed in and the consumer decorates around it.
188
188
  */
189
189
  ...cellRender ? {
190
- DayButton: (dayButtonProps) => /* @__PURE__ */ jsx(Fragment, { children: cellRender(dayButtonProps.day.date, { originNode: /* @__PURE__ */ jsx(DayButton, { ...dayButtonProps }) }) })
190
+ DayButton: (dayButtonProps) => /* @__PURE__ */ jsx(Fragment, { children: cellRender(dayButtonProps.day.date, {
191
+ originNode: /* @__PURE__ */ jsx(DayButton, { ...dayButtonProps })
192
+ }) })
191
193
  } : {},
192
194
  /*
193
195
  * A consumer's `components` MERGE with ours; they must not replace the object.
@@ -205,5 +207,7 @@ function Calendar({
205
207
  );
206
208
  }
207
209
  export {
208
- Calendar
210
+ Calendar,
211
+ DayButton,
212
+ dateMatchModifiers
209
213
  };
@@ -70,6 +70,7 @@ function DatePicker(props) {
70
70
  defaultOpen = false,
71
71
  onOpenChange,
72
72
  inputReadOnly,
73
+ width,
73
74
  preserveInvalidOnBlur,
74
75
  placement = "bottom-start",
75
76
  renderExtraFooter,
@@ -580,11 +581,16 @@ function DatePicker(props) {
580
581
  "data-size": size,
581
582
  "data-status": status,
582
583
  "data-variant": variant,
584
+ "data-width": width,
583
585
  "aria-disabled": allDisabled ? true : void 0,
584
586
  "data-disabled": allDisabled ? "" : void 0,
585
587
  "data-state": open ? "open" : "closed",
586
588
  className: cn(
587
589
  "ui-control ui-control-composite-field",
590
+ // `bounded` emits NO utility on purpose — control.css owns it, and a utility here
591
+ // would win the layer order and kill the token (gh#366, gh#375, gh#799).
592
+ width === "auto" && "w-auto",
593
+ width === "full" && "w-full",
588
594
  "aria-invalid:border-destructive",
589
595
  CONTROL_VARIANT_CHROME_CLASS[variant ?? "outlined"],
590
596
  CONTROL_STATUS_CHROME_CLASS,
@@ -610,6 +616,12 @@ function DatePicker(props) {
610
616
  /* @__PURE__ */ jsx(
611
617
  Input,
612
618
  {
619
+ "data-width": width,
620
+ className: cn(
621
+ width === "auto" && "w-auto",
622
+ width === "full" && "w-full",
623
+ width === "bounded" && "w-[var(--control-bounded-width)] max-w-full"
624
+ ),
613
625
  id: rootId,
614
626
  name: displayIsCanonical ? resolvedName : "",
615
627
  ref,
@@ -72,3 +72,5 @@ export type { UploadRequestContext, UploadResult, UploadRejection } from "./uplo
72
72
  export type { UploadItemActions } from "./upload-types.js";
73
73
  export { Attachments } from "./attachments.js";
74
74
  export type { AttachmentsProp, AttachmentsProps, AttachmentsItemProp, AttachmentsPlaceholderProp, AttachmentsOverflowProp, AttachmentsRefProp, } from "./attachments.js";
75
+ export { DayButton, dateMatchModifiers } from "./calendar.js";
76
+ export type { DateRange, Modifiers, DayPickerProps, DayButtonProps, Matcher, CalendarDay, DateLib, } from "./calendar.js";
@@ -60,6 +60,7 @@ import { Segmented } from "./segmented.js";
60
60
  import { BranchScopePicker } from "./branch-scope-picker.js";
61
61
  import { TimeRangePicker } from "./time-range-picker.js";
62
62
  import { Attachments } from "./attachments.js";
63
+ import { DayButton, dateMatchModifiers } from "./calendar.js";
63
64
  export {
64
65
  Attachments,
65
66
  BranchScopePicker,
@@ -78,6 +79,7 @@ export {
78
79
  CommandList,
79
80
  CommandPalette,
80
81
  DatePicker,
82
+ DayButton,
81
83
  Field,
82
84
  Form,
83
85
  FormErrors,
@@ -127,6 +129,7 @@ export {
127
129
  Upload,
128
130
  collectUploadCommitActions,
129
131
  createUploadItem,
132
+ dateMatchModifiers,
130
133
  useFormLayout,
131
134
  usePasswordStrength,
132
135
  useUploadDraft
@@ -6,4 +6,4 @@ export type { TimePickerDisabledTimeProp, TimePickerProp, TimePickerProp as Time
6
6
  * canonical): form-submittable (give it a `name`), screen-reader friendly, and e2e-testable by
7
7
  * filling the input.
8
8
  */
9
- export declare function TimePicker({ value: controlledValue, defaultValue, onValueChange, placeholder, disabled, className, id, name, minuteStep, hourStep, changeOnScroll, secondStep, showSeconds: showSecondsProp, use12Hours, format, open: openProp, defaultOpen, onOpenChange, inputReadOnly, preserveInvalidOnBlur, placement, renderExtraFooter, size, status, variant, ref, disabledTime, hideDisabledOptions, showNow, needConfirm, allowClear, ...ariaProps }: TimePickerProp): React.JSX.Element;
9
+ export declare function TimePicker({ value: controlledValue, defaultValue, onValueChange, placeholder, disabled, className, id, name, minuteStep, hourStep, changeOnScroll, secondStep, showSeconds: showSecondsProp, use12Hours, format, open: openProp, defaultOpen, onOpenChange, inputReadOnly, width, preserveInvalidOnBlur, placement, renderExtraFooter, size, status, variant, ref, disabledTime, hideDisabledOptions, showNow, needConfirm, allowClear, ...ariaProps }: TimePickerProp): React.JSX.Element;
@@ -377,6 +377,7 @@ function TimePicker({
377
377
  defaultOpen = false,
378
378
  onOpenChange,
379
379
  inputReadOnly,
380
+ width,
380
381
  preserveInvalidOnBlur,
381
382
  placement = "bottom-end",
382
383
  renderExtraFooter,
@@ -428,6 +429,7 @@ function TimePicker({
428
429
  /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx("div", { className: cn("relative", className), children: /* @__PURE__ */ jsx(
429
430
  Input,
430
431
  {
432
+ "data-width": width,
431
433
  id,
432
434
  ref,
433
435
  size,
@@ -445,7 +447,12 @@ function TimePicker({
445
447
  "aria-haspopup": "dialog",
446
448
  "aria-controls": open ? dialogId : void 0,
447
449
  ...fieldA11y,
448
- className: "tabular-nums",
450
+ className: cn(
451
+ "tabular-nums",
452
+ width === "auto" && "w-auto",
453
+ width === "full" && "w-full",
454
+ width === "bounded" && "w-[var(--control-bounded-width)] max-w-full"
455
+ ),
449
456
  trailingIcon: /* @__PURE__ */ jsx("span", { className: "ui-time-picker-affix", children: showClear ? /* @__PURE__ */ jsx(
450
457
  "button",
451
458
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "28.3.1",
3
+ "version": "28.4.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -540,6 +540,11 @@ export type PermissionMatrixProp = {
540
540
  * live stream (chat, log tail, streaming response, activity feed).
541
541
  */
542
542
  export type ScrollAreaAnchorProp = "none" | "bottom";
543
+ /**
544
+ * Whether the scrollbar is always drawn, or left to the platform (gh#798). `auto` is the platform
545
+ * decision and the default; `always` forces a classic, space-occupying bar.
546
+ */
547
+ export type ScrollAreaScrollbarProp = "auto" | "always";
543
548
  /** @see ScrollArea */
544
549
  /**
545
550
  * @see ScrollArea — which AXES scroll. This is not decoration: it IS the `overflow` the element
@@ -570,6 +575,23 @@ export type ScrollAreaProp = {
570
575
  */
571
576
  viewportRef?: React.Ref<HTMLDivElement>;
572
577
  /** Edge the viewport sticks to as content grows. Default `none` (inert). */
578
+ /**
579
+ * Whether the scrollbar is drawn ALWAYS or only when the platform feels like it (gh#798).
580
+ *
581
+ * Default `auto` — today's behaviour, unchanged: the browser decides, styled from the
582
+ * `--scroll-area-*` tokens. On macOS/iPadOS with the system default "Show scroll bars: when
583
+ * scrolling", that decision is an OVERLAY bar, which exists only while the reader is already
584
+ * scrolling — so the tokens style a bar nobody sees, and a wide area does not LOOK scrollable.
585
+ * Measured by a consumer: clientWidth 727, scrollWidth 3476, and `offsetHeight - clientHeight`
586
+ * of 2px (the border, twice) — no bar occupies layout at all. A manager reading a 31-column
587
+ * roster said it showed "only five days".
588
+ *
589
+ * `always` forces a classic, space-occupying bar via `::-webkit-scrollbar`, which Chrome and
590
+ * Safari honour even in overlay mode, painted from the same tokens. Reach for it where the
591
+ * affordance IS the information — a wide table, a roster, a board. Leave it `auto` for a chat
592
+ * stream, where a permanent bar is noise (gh#311).
593
+ */
594
+ scrollbar?: ScrollAreaScrollbarProp;
573
595
  anchor?: ScrollAreaAnchorProp;
574
596
  /**
575
597
  * Distance in px from the bottom edge inside which the reader still counts as "at the bottom"
@@ -770,6 +770,20 @@ export type CalendarFooterProp = {
770
770
  };
771
771
  /** Shared picker chrome. Placement uses logical start/end so RTL follows the locale. */
772
772
  export type PickerChromeProp = {
773
+ /**
774
+ * How the field takes its inline space, exactly as `Select` means it (gh#799).
775
+ *
776
+ * CONSUMER-RULES §5 says a control outside a form takes `width="auto"`, and `Select` has carried
777
+ * `width` since gh#375 — the pickers did not, so the one control in a toolbar with a KNOWN
778
+ * content width was the one that could not be sized. Measured in a consumer toolbar at 1440px:
779
+ * a month picker showing `2026/09` took 275px, against the 144px box it had before the migration,
780
+ * because with no prop it falls to the flex default and stretches.
781
+ *
782
+ * `auto` shrink-wraps to the display format; `full` fills the line; `bounded` holds one width
783
+ * while the value's length varies (its width is owned by the `[data-width="bounded"]` rule in
784
+ * control.css, NOT by a utility — see the note there).
785
+ */
786
+ width?: ControlWidthProp;
773
787
  open?: boolean;
774
788
  defaultOpen?: boolean;
775
789
  onOpenChange?: (open: boolean) => void;
@@ -1810,6 +1810,15 @@ export declare const COMPONENT_PROP_REGISTRY: {
1810
1810
  readonly reason: 'Edge the scrolling viewport sticks to as content grows ("none" | "bottom"). A closed union, not a boolean pair — the third edge ("top", for an inverted feed) is a value, not another flag.';
1811
1811
  }];
1812
1812
  };
1813
+ readonly ScrollAreaScrollbarProp: {
1814
+ readonly group: "data-display";
1815
+ readonly file: "components/data-display.prop.ts";
1816
+ readonly vocabulary: readonly [{
1817
+ readonly field: "scrollbar";
1818
+ readonly local: true;
1819
+ readonly reason: 'Whether the scrollbar is always drawn or left to the platform ("auto" | "always"). Not a width or a colour — those are the --scroll-area-* tokens; this names WHO decides the bar exists, because on macOS/iPadOS the platform decides "only while scrolling" and a wide area then never looks scrollable (gh#798).';
1820
+ }];
1821
+ };
1813
1822
  readonly ScrollAreaOrientationProp: {
1814
1823
  readonly group: "data-display";
1815
1824
  readonly file: "components/data-display.prop.ts";
@@ -2203,6 +2203,17 @@ const COMPONENT_PROP_REGISTRY = {
2203
2203
  }
2204
2204
  ]
2205
2205
  },
2206
+ ScrollAreaScrollbarProp: {
2207
+ group: "data-display",
2208
+ file: "components/data-display.prop.ts",
2209
+ vocabulary: [
2210
+ {
2211
+ field: "scrollbar",
2212
+ local: true,
2213
+ reason: 'Whether the scrollbar is always drawn or left to the platform ("auto" | "always"). Not a width or a colour \u2014 those are the --scroll-area-* tokens; this names WHO decides the bar exists, because on macOS/iPadOS the platform decides "only while scrolling" and a wide area then never looks scrollable (gh#798).'
2214
+ }
2215
+ ]
2216
+ },
2206
2217
  ScrollAreaOrientationProp: {
2207
2218
  group: "data-display",
2208
2219
  file: "components/data-display.prop.ts",
@@ -1326,7 +1326,9 @@
1326
1326
  cursor: not-allowed;
1327
1327
  }
1328
1328
 
1329
- .ui-control-trigger[data-width="bounded"] {
1329
+ .ui-control-trigger[data-width="bounded"],
1330
+
1331
+ .ui-control[data-width="bounded"] {
1330
1332
  inline-size: var(--control-bounded-width);
1331
1333
  max-inline-size: 100%;
1332
1334
  }
@@ -2325,8 +2327,15 @@
2325
2327
 
2326
2328
  .ui-calendar[data-width="full"] .ui-calendar-day-button {
2327
2329
  inline-size: 100%;
2328
-
2329
2330
  max-inline-size: none;
2331
+ block-size: auto;
2332
+ min-block-size: var(--control-height);
2333
+
2334
+ flex-direction: column;
2335
+ align-items: stretch;
2336
+ justify-content: flex-start;
2337
+ text-align: start;
2338
+ white-space: normal;
2330
2339
  }
2331
2340
 
2332
2341
  .ui-calendar-month {
@@ -958,6 +958,24 @@
958
958
  overflow: auto;
959
959
  }
960
960
 
961
+ .ui-scroll-area[data-scrollbar="always"] {
962
+ scrollbar-width: auto;
963
+ }
964
+
965
+ .ui-scroll-area[data-scrollbar="always"]::-webkit-scrollbar {
966
+ inline-size: var(--scroll-area-always-bar-size);
967
+ block-size: var(--scroll-area-always-bar-size);
968
+ }
969
+
970
+ .ui-scroll-area[data-scrollbar="always"]::-webkit-scrollbar-track {
971
+ background: var(--scroll-area-track-color, transparent);
972
+ }
973
+
974
+ .ui-scroll-area[data-scrollbar="always"]::-webkit-scrollbar-thumb {
975
+ background: var(--scroll-area-thumb-color, hsl(var(--border)));
976
+ border-radius: var(--scroll-area-always-thumb-radius);
977
+ }
978
+
961
979
  .ui-scroll-area:is([data-orientation="horizontal"], [data-orientation="both"])
962
980
  > .ui-scroll-area-content {
963
981
  inline-size: max-content;
@@ -8,5 +8,8 @@
8
8
 
9
9
  --scroll-area-scrollbar-space: auto;
10
10
 
11
+ --scroll-area-always-bar-size: 0.625rem;
12
+ --scroll-area-always-thumb-radius: var(--radius-pill);
13
+
11
14
  --scroll-area-anchor-offset: 3rem;
12
15
  }
@@ -16,8 +16,12 @@ import { Flex, PageContainer, ResponsiveGrid } from "@godxjp/ui/layout";
16
16
  /**
17
17
  * ScrollArea — a native scrolling box. ALWAYS give it an explicit height/max-height
18
18
  * (vertical) or width (horizontal), or nothing ever overflows and no scrollbar appears.
19
- * `orientation` decides which axes may scroll; the browser draws the bar, styled from
20
- * the --scroll-area-* tokens. Composed only from real @godxjp/ui components.
19
+ * `orientation` decides which axes may scroll. By default the PLATFORM draws the bar,
20
+ * styled from the --scroll-area-* tokens — and on macOS/iPadOS with the system default
21
+ * "Show scroll bars: when scrolling" that bar is an OVERLAY: it exists only while the
22
+ * reader is already scrolling, so a wide area does not look scrollable at rest (gh#798).
23
+ * `scrollbar="always"` forces a classic bar that occupies layout, from the same tokens.
24
+ * Composed only from real @godxjp/ui components.
21
25
  */
22
26
  const entries = Array.from(
23
27
  { length: 18 },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "28.3.1",
4
- "godxUiMcp": "28.3.1",
3
+ "version": "28.4.0",
4
+ "godxUiMcp": "28.4.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",