@godxjp/ui 23.4.5 → 23.4.7

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.
@@ -73,6 +73,7 @@ import {
73
73
  tableCellPaddingClass,
74
74
  tableRowHeightClass
75
75
  } from "../../lib/control-styles.js";
76
+ import { isDevelopment } from "../../lib/dev.js";
76
77
  function resolveColumnHideBelow(col) {
77
78
  if (col.hideBelow) return col.hideBelow;
78
79
  if (col.hiddenOnMobile) return "md";
@@ -796,7 +797,7 @@ DataTable.Content = function DataTableContent() {
796
797
  const fixedTableLayout = scroll?.x !== void 0 || visibleColumns.some((col) => col.ellipsis || columnWidth(col.width).style !== void 0);
797
798
  const missingHeaderNames = visibleColumns.filter((col) => isEmptyHeader(col.header) && !col.ariaLabel).map((col) => col.key).join("|");
798
799
  React.useEffect(() => {
799
- if (process.env.NODE_ENV === "production" || !missingHeaderNames) return;
800
+ if (!isDevelopment() || !missingHeaderNames) return;
800
801
  for (const key of missingHeaderNames.split("|")) {
801
802
  console.warn(
802
803
  `[DataTable] Column "${key}" renders a <th> with no visible or accessible text. Give it a visible \`header\`, or set \`ariaLabel\` (e.g. "Actions"/"Select") so screen readers can announce the column and axe reports no empty-table-header violation.`
@@ -10,6 +10,7 @@ import { FieldIdentityContext, FieldNameContext, mergeAriaIds } from "../../lib/
10
10
  import { useOptionalAppContext } from "../../app/app-provider.js";
11
11
  import { useFormLayout } from "./form.js";
12
12
  import { firstBagMessage, useClaimErrorKey, useFormErrorsRegistry } from "./form-errors.js";
13
+ import { isDevelopment } from "../../lib/dev.js";
13
14
  const toCssLength = (v) => typeof v === "number" ? `${v}px` : v;
14
15
  const FOCUSABLE_SELECTOR = 'input:not([type="hidden"]), select, textarea, button, [tabindex]:not([tabindex="-1"])';
15
16
  function FormField({
@@ -55,7 +56,7 @@ function FormField({
55
56
  const fieldKey = field ?? name ?? id;
56
57
  const emitFieldNames = useOptionalAppContext()?.emitFieldNames ?? false;
57
58
  const isStatic = staticText !== void 0;
58
- if (!isStatic && typeof process !== "undefined" && process.env?.NODE_ENV !== "production" && !React.isValidElement(children)) {
59
+ if (!isStatic && isDevelopment() && !React.isValidElement(children)) {
59
60
  console.warn(
60
61
  "FormField expects a single React element child to receive aria-describedby/aria-errormessage; the helper text and error message will not be associated with the control. Pass plain text via `staticText` instead of `children` for a read-only value row."
61
62
  );
@@ -8,6 +8,7 @@ import { numberFormat } from "../../lib/intl-cache.js";
8
8
  import { cn } from "../../lib/utils.js";
9
9
  import { Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
10
10
  import { Button } from "./button.js";
11
+ import { isDevelopment } from "../../lib/dev.js";
11
12
  const GroupContext = React.createContext(null);
12
13
  const DEFAULT_ICON = /* @__PURE__ */ jsx(FileText, { "aria-hidden": "true" });
13
14
  const BUTTON_VARIANT = {
@@ -67,7 +68,7 @@ const FloatButtonRoot = React.forwardRef(function FloatButton({
67
68
  const mergedContent = isRenderable(content) ? content : description;
68
69
  const hasContent = isRenderable(mergedContent);
69
70
  const mergedIcon = !hasContent && icon == null ? DEFAULT_ICON : icon;
70
- if (process.env.NODE_ENV !== "production" && mergedShape === "circle" && hasContent) {
71
+ if (isDevelopment() && mergedShape === "circle" && hasContent) {
71
72
  console.warn(
72
73
  "[@godxjp/ui] FloatButton: `content` is supported only when `shape` is `square` \u2014 a circle has no room for a line of text."
73
74
  );
@@ -153,7 +154,7 @@ function FloatButtonGroup({
153
154
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
154
155
  const isControlled = open != null;
155
156
  const isOpen = isControlled ? open : uncontrolledOpen;
156
- if (process.env.NODE_ENV !== "production" && isControlled && !trigger) {
157
+ if (isDevelopment() && isControlled && !trigger) {
157
158
  console.warn(
158
159
  "[@godxjp/ui] FloatButton.Group: `open` needs to be used together with `trigger`."
159
160
  );
@@ -6,6 +6,7 @@ import { cn } from "../../lib/utils.js";
6
6
  import { useTranslation } from "../../i18n/use-translation.js";
7
7
  import { Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
8
8
  import { Textarea } from "../data-entry/textarea.js";
9
+ import { isDevelopment } from "../../lib/dev.js";
9
10
  const COPIED_RESET_MS = 3e3;
10
11
  const DECORATION_ELEMENTS = [
11
12
  ["strong", "strong"],
@@ -409,7 +410,7 @@ const TextBase = React.forwardRef((props, ref) => {
409
410
  const ellipsisRowCount = ellipsis ? ellipsisRows(ellipsis) : 0;
410
411
  const effectiveClamp = ellipsisRowCount > 1 ? ellipsisRowCount : ellipsis ? void 0 : clampLines;
411
412
  const truncating = (truncate === true || ellipsisRowCount === 1) && effectiveClamp === void 0;
412
- if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
413
+ if (isDevelopment()) {
413
414
  if (clamp !== void 0 && clampLines === void 0) {
414
415
  console.warn(`Text: \`clamp\` must be a finite number \u2265 1 (got ${String(clamp)}); ignored.`);
415
416
  }
@@ -7,6 +7,7 @@ import { useTranslation } from "../../i18n/use-translation.js";
7
7
  import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "../feedback/sheet.js";
8
8
  import { NavSurfaceProvider } from "./nav-surface.js";
9
9
  import { TopbarItem } from "./topbar-item.js";
10
+ import { isDevelopment } from "../../lib/dev.js";
10
11
  function useAppShellNavigationMode() {
11
12
  return useMediaQuery("(width <= 56.25rem)") ? "drawer" : "docked";
12
13
  }
@@ -59,7 +60,7 @@ function AppShell({
59
60
  }
60
61
  };
61
62
  const hasTopbarContent = topbar !== void 0 || topbarLeft !== void 0 || topbarRight !== void 0 || logo !== void 0;
62
- if (process.env.NODE_ENV !== "production" && topbar !== void 0) {
63
+ if (isDevelopment() && topbar !== void 0) {
63
64
  const ignored = [
64
65
  topbarLeft !== void 0 && "topbarLeft",
65
66
  topbarRight !== void 0 && "topbarRight"
@@ -9,6 +9,7 @@ import { Progress } from "../data-display/progress.js";
9
9
  import { Text } from "../general/typography.js";
10
10
  import { CenteredShell } from "./centered-shell.js";
11
11
  import { dateTimeFormat, numberFormat } from "../../lib/intl-cache.js";
12
+ import { isDevelopment } from "../../lib/dev.js";
12
13
  const STATUS_META = {
13
14
  400: { icon: TriangleAlert, tone: "warning" },
14
15
  403: { icon: ShieldAlert, tone: "warning" },
@@ -23,7 +24,7 @@ function singleAction(action) {
23
24
  const flattened = React.Children.toArray(action).flatMap(
24
25
  (child) => React.isValidElement(child) && child.type === React.Fragment ? React.Children.toArray(child.props.children) : [child]
25
26
  );
26
- if (flattened.length > 1 && process.env.NODE_ENV !== "production") {
27
+ if (flattened.length > 1 && isDevelopment()) {
27
28
  console.error(
28
29
  "[@godxjp/ui] ErrorSurface: `action` takes EXACTLY ONE recovery action. The extra elements were dropped. Put support contact or secondary guidance in `description`."
29
30
  );
@@ -5,6 +5,7 @@ import { Eye, EyeOff } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { cn } from "../../lib/utils.js";
7
7
  import { Input } from "../data-entry/input.js";
8
+ import { isDevelopment } from "../../lib/dev.js";
8
9
  const PasswordInput = React.forwardRef(
9
10
  ({ className, visibilityToggle = true, iconRender, suffix, trailingIcon, allowClear, ...props }, ref) => {
10
11
  const { t } = useTranslation();
@@ -19,7 +20,7 @@ const PasswordInput = React.forwardRef(
19
20
  onVisibleChange?.(next);
20
21
  };
21
22
  const toggleOwnsTrailing = showToggle;
22
- if (toggleOwnsTrailing && process.env?.NODE_ENV !== "production" && (suffix != null || trailingIcon != null || allowClear)) {
23
+ if (toggleOwnsTrailing && isDevelopment() && (suffix != null || trailingIcon != null || allowClear)) {
23
24
  console.warn(
24
25
  "[godxjp-ui] PasswordInput: the reveal toggle owns the trailing slot, so `suffix` / `trailingIcon` / `allowClear` are ignored. Use `leadingIcon` (or `prefix`), or pass `visibilityToggle={false}` to hand the slot back."
25
26
  );
@@ -3,6 +3,7 @@ import * as React from "react";
3
3
  import { Separator as AriaSeparator } from "react-aria-components";
4
4
  import { padStepToken } from "../../lib/variants.js";
5
5
  import { cn } from "../../lib/utils.js";
6
+ import { isDevelopment } from "../../lib/dev.js";
6
7
  const Separator = React.forwardRef(
7
8
  ({
8
9
  className,
@@ -20,7 +21,7 @@ const Separator = React.forwardRef(
20
21
  }, ref) => {
21
22
  const hasLabel = typeof label === "string" && label.trim() !== "";
22
23
  const labelled = hasLabel && orientation === "horizontal";
23
- if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
24
+ if (isDevelopment()) {
24
25
  if (hasLabel && orientation === "vertical") {
25
26
  console.warn(
26
27
  'Separator: `label` is rendered on orientation="horizontal" only; it was ignored. Use a horizontal rule for a day divider / unread watermark.'
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `isDevelopment()` — the ONE place this package is allowed to ask whether it is in a dev build.
3
+ *
4
+ * WHY THIS EXISTS, and it is a crash rather than a tidiness question. Eight shipped modules read
5
+ * `process.env.NODE_ENV` to decide whether to emit a development warning. Three of them guarded
6
+ * the read with `typeof process !== "undefined"`; five did not, and `process` is not a browser
7
+ * global. Nothing in the build replaces it either — `tsup.config.ts` declares no `define`, and
8
+ * `grep -o 'process\.env' dist` on the published tarball of 23.4.4 returns 9 hits, raw.
9
+ *
10
+ * Measured, not theorised: this repo's own preview app (Vite 8, dev mode) renders
11
+ * `/isolate/data-entry-password-strength` as **"Preview render failed — process is not defined"**,
12
+ * six DOM nodes and nothing else. `PasswordInput` is one of the five unguarded modules and that
13
+ * page renders it. A bundler that happens to define `process.env.NODE_ENV` papers over it, which
14
+ * is exactly why it survived: most consumers' toolchains do, and the ones that do not get a blank
15
+ * screen rather than a warning.
16
+ *
17
+ * The helper reads the guard ONCE so the two halves cannot drift apart again, and it fails SAFE:
18
+ * where `process` is absent we cannot tell dev from production, so the answer is `false` and the
19
+ * warnings go quiet. A missing warning is a cost; a component that throws while rendering is not
20
+ * a cost, it is a broken page.
21
+ *
22
+ * A new bare `process.env` read in `src/components/**` is a CI failure — see
23
+ * `src/lib/__tests__/dev-guard.test.ts`.
24
+ *
25
+ * TWO THINGS ABOUT THE SHAPE BELOW, and the first one is why the three "guarded" modules were
26
+ * only half guarded.
27
+ *
28
+ * NO OPTIONAL CHAINING ON `process.env`. It does not survive the build. Written as
29
+ * `process.env?.NODE_ENV`, the transformed output measured in this repo's own vitest pipeline is
30
+ *
31
+ * return typeof process !== "undefined" && process.env.NODE_ENV !== "production";
32
+ *
33
+ * — the `?.` is GONE, because a bundler `define` matches the text `process.env` and substitutes
34
+ * through it, discarding the chain. So `?.` is not a guard here; it reads like one, which is
35
+ * worse than nothing. `typeof process` does survive (measured in the same dump), and a truthiness
36
+ * test on `process.env` survives as a truthiness test whatever it is replaced with.
37
+ *
38
+ * THE LITERAL IS KEPT ON THE LAST LINE ON PURPOSE. `process.env.NODE_ENV` is the exact text every
39
+ * bundler's `define` looks for, and that substitution is what lets a consumer's production build
40
+ * fold these branches away and drop the warning strings entirely. Reading the global indirectly
41
+ * (`globalThis.process`) would be equally crash-proof and would cost every consumer that
42
+ * dead-code elimination, so the order of the checks does the work instead: the guard runs first,
43
+ * and the replaceable literal is only reached once `env` is known to exist.
44
+ */
45
+ export declare function isDevelopment(): boolean;
@@ -0,0 +1,7 @@
1
+ function isDevelopment() {
2
+ if (typeof process === "undefined" || !process.env) return false;
3
+ return process.env.NODE_ENV !== "production";
4
+ }
5
+ export {
6
+ isDevelopment
7
+ };
@@ -27,6 +27,10 @@
27
27
  color: hsl(var(--muted-foreground));
28
28
  }
29
29
 
30
+ .ui-chart .recharts-legend-item-text {
31
+ color: hsl(var(--muted-foreground)) !important;
32
+ }
33
+
30
34
  .ui-chart .recharts-default-tooltip {
31
35
  border-radius: var(--radius);
32
36
  border: 1px solid hsl(var(--border)) !important;
@@ -219,6 +219,32 @@
219
219
  box-shadow: inset 0 var(--tabs-card-rail-border-width) 0 hsl(var(--border));
220
220
  }
221
221
 
222
+ [data-slot="tabs"][data-variant="card"][data-placement="start"] [data-slot="tabs-list"],
223
+ [data-slot="tabs"][data-variant="editable-card"][data-placement="start"]
224
+ [data-slot="tabs-list"] {
225
+ box-shadow: inset calc(-1 * var(--tabs-card-rail-border-width)) 0 0 hsl(var(--border));
226
+ }
227
+
228
+ [data-slot="tabs"][data-variant="card"][data-placement="end"] [data-slot="tabs-list"],
229
+ [data-slot="tabs"][data-variant="editable-card"][data-placement="end"]
230
+ [data-slot="tabs-list"] {
231
+ box-shadow: inset var(--tabs-card-rail-border-width) 0 0 hsl(var(--border));
232
+ }
233
+
234
+ [dir="rtl"] [data-slot="tabs"][data-variant="card"][data-placement="start"]
235
+ [data-slot="tabs-list"],
236
+ [dir="rtl"] [data-slot="tabs"][data-variant="editable-card"][data-placement="start"]
237
+ [data-slot="tabs-list"] {
238
+ box-shadow: inset var(--tabs-card-rail-border-width) 0 0 hsl(var(--border));
239
+ }
240
+
241
+ [dir="rtl"] [data-slot="tabs"][data-variant="card"][data-placement="end"]
242
+ [data-slot="tabs-list"],
243
+ [dir="rtl"] [data-slot="tabs"][data-variant="editable-card"][data-placement="end"]
244
+ [data-slot="tabs-list"] {
245
+ box-shadow: inset calc(-1 * var(--tabs-card-rail-border-width)) 0 0 hsl(var(--border));
246
+ }
247
+
222
248
  .ui-tabs-tab-remove {
223
249
  display: inline-flex;
224
250
  flex: 0 0 auto;
@@ -121,6 +121,7 @@
121
121
  }
122
122
 
123
123
  .ui-data-table-sort-button {
124
+ position: relative;
124
125
  display: inline-flex;
125
126
  align-items: center;
126
127
  gap: var(--space-inline-xs);
@@ -132,6 +133,18 @@
132
133
  outline: none;
133
134
  }
134
135
 
136
+ .ui-data-table-sort-button::after {
137
+ content: "";
138
+ position: absolute;
139
+ inset-block-start: 50%;
140
+ inset-inline-start: 50%;
141
+ translate: -50% -50%;
142
+ min-inline-size: var(--touch-target-min);
143
+ min-block-size: var(--touch-target-min);
144
+ inline-size: 100%;
145
+ block-size: 100%;
146
+ }
147
+
135
148
  .ui-data-table-bulk {
136
149
  display: flex;
137
150
  align-items: center;
@@ -600,6 +600,26 @@ export default function Demo() {
600
600
  size="lg"
601
601
  items={journalItems}
602
602
  />
603
+ {/* CARD on the vertical axis. `card` + `bottom` above had shipped upside down, and
604
+ `start`/`end` had no card example at all — so the rail, which is drawn with a
605
+ box-shadow and therefore cannot be logical, had nothing to be measured against
606
+ in either direction. */}
607
+ <Tabs
608
+ id="antd-card-start"
609
+ defaultValue="pending"
610
+ variant="card"
611
+ tabPlacement="start"
612
+ size="sm"
613
+ items={journalItems}
614
+ />
615
+ <Tabs
616
+ id="antd-card-end"
617
+ defaultValue="pending"
618
+ variant="card"
619
+ tabPlacement="end"
620
+ size="sm"
621
+ items={journalItems}
622
+ />
603
623
  </Flex>
604
624
  </CardContent>
605
625
  </Card>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "23.4.5",
4
- "godxUiMcp": "23.4.5",
3
+ "version": "23.4.7",
4
+ "godxUiMcp": "23.4.7",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",